# πŸͺ Sotce Net β€” Agent Context > A paid diary network by **Sotce** & **Aesthetic Computer** β€” live at [sotce.net](https://sotce.net). --- ## 🧠 What Is This? A subscription diary platform. The writer (**@amelia** / Sotce) publishes pages; subscribers read, touch, and chat. Private digital magazine with real-time social features and a warm, paper-like, cookie-themed aesthetic. --- ## πŸ—οΈ Architecture **One giant Netlify function** β€” `sotce-net.mjs` (~10,270 lines). No framework, no build step. Server-rendered HTML via tagged template literals. Handles GET routes (full HTML pages) and POST/DELETE routes (REST API). ### βš™οΈ Stack | Layer | Tech | |-------|------| | πŸ–₯️ Runtime | Netlify Functions (Node.js serverless) | | 🎨 Frontend | Vanilla JS, Canvas API (page rendering), Monaco Editor (chat input) | | πŸ” Auth | Auth0 SPA SDK β€” tenant `sotce` at `sotce.us.auth0.com` | | πŸ’³ Payments | Stripe subscriptions via Checkout Sessions | | πŸ—„οΈ Database | MongoDB β€” `sotce-pages`, `sotce-asks`, `sotce-touches`, `chat-sotce`, `@handles` | | ⚑ Cache | Redis (subscription status via `kv.mjs`) | | πŸ’¬ Real-time | WebSocket chat via session server (room: `chat-sotce`) | | πŸ”€ Fonts | Helvetica (custom woff/ttf), Carlito (Google), Wingdings 2 | ### πŸ“ Key Files | File | What | |------|------| | `system/netlify/functions/sotce-net.mjs` | πŸͺ **The entire app** | | `system/backend/sotce-net-constants.mjs` | πŸ’³ Stripe keys, price/product IDs, SMTP creds | | `system/backend/authorization.mjs` | πŸ” `authorize()`, `hasAdmin()`, `handleFor()`, `deleteUser()` | | `system/backend/database.mjs` | πŸ—„οΈ MongoDB `connect()` β†’ `{ db, disconnect }` | | `system/backend/kv.mjs` | ⚑ Redis `connect/get/set/del/disconnect` | | `system/backend/http.mjs` | πŸ“‘ `respond(status, body, headers)` | | `system/backend/shell.mjs` | πŸ“ `shell.log()` / `.warn()` / `.error()` | | `system/public/aesthetic.computer/lib/chat.mjs` | πŸ’¬ Client Chat class (WebSocket wrapper) | ### πŸ–ΌοΈ Assets Served from `https://assets.aesthetic.computer/sotce-net/` (prod) or `/assets/sotce-net/` (dev). Key: `cookie.png`, `cookie-open.png`, `thumbnail.png`, `helvetica.woff`, `helvetica-bold.woff`, `Wingdings 2.ttf`. --- ## πŸ›£οΈ Routes ### 🌐 GET (HTML Pages) | Route | What | |-------|------| | `/` | 🏠 Gate (login/subscribe) or Garden (page feed) | | `/chat` | πŸ’¬ Real-time chat | | `/gate` | πŸšͺ Login/subscribe screen | | `/write` | ✍️ Page editor (admin) | | `/ask` | ❓ Question submission (subscribers) | | `/respond` | πŸ“ Question response editor (admin) | | `/page/:n` | πŸ“„ Deep link to diary page | | `/q/:n` | ❓ Deep link to Q&A | | `/privacy-policy` | πŸ“œ Privacy policy | ### πŸ“‘ API Endpoints | Method | Path | Auth | What | |--------|------|------|------| | GET | `/subscribers` | β€” | Subscriber count | | POST | `/subscribe` | β€” | Stripe Checkout session | | POST | `/subscribed` | Bearer | Check sub + fetch pages/questions | | POST | `/cancel` | Bearer | Cancel subscription | | POST | `/write-a-page` | πŸ‘‘ Admin | Draft CRUD + publish | | POST | `/touch-a-page` | πŸ”‘ Sub | Record page touch | | POST | `/delete-account` | Bearer | Full account nuke | | POST | `/ask` | πŸ”‘ Sub | Submit question | | GET | `/asks` | Bearer | User's own questions | | GET | `/asks/pending` | πŸ‘‘ Admin | Unanswered questions | | POST | `/ask/:id/respond` | πŸ‘‘ Admin | Answer question | | POST | `/ask/:id/save-draft` | πŸ‘‘ Admin | Save draft response | | POST | `/ask/:id/reject` | πŸ‘‘ Admin | Reject question | | DELETE | `/ask/:id` | Owner | Delete own pending question | | DELETE | `/asks/clear-all` | πŸ‘‘ Admin | Clear all (dev tool) | --- ## πŸ‘€ User Roles | Role | Access | |------|--------| | πŸ”’ Logged out | Gate, subscriber count, privacy policy | | πŸ“§ Unverified | Email verification prompt (polls Auth0) | | βœ… Verified (no sub) | Subscribe button, read-only chat | | πŸ”‘ Subscribed | Garden, chat, touch pages, ask questions | | πŸ‘‘ Admin | Write pages, respond to Qs, pending Qs. Emails: `me@jas.life`, `sotce.net@gmail.com` (bypass Stripe via `ADMIN_EMAILS`) | --- ## 🎨 UI Concepts - **πŸšͺ Gate** β€” Cookie login screen. Click cookie β†’ enter garden. Status-specific buttons per role. - **🌻 Garden** β€” Canvas-rendered page feed. 4:5 cards, one at a time, swipe/drag/arrow to navigate. Card flip (ear tap) reveals who "touched" it. - **πŸ’¬ Chat** β€” WebSocket real-time. Monaco Editor input with @handle autocomplete, syntax highlighting for `-5-` (diary links) and `*3*` (Q&A links), URL auto-linking, unread badge. - **✍️ Page Editor** β€” Admin. 19-line max, justified+hyphenated, draft/keep/crumple/publish. - **❓ Ask Editor** β€” Subscriber. 5-line max, localStorage draft, "my questions" list view. - **πŸ“ Respond Editor** β€” Admin. Prev/next Qs, auto-draft to server, dynamic response line count. --- ## πŸ—„οΈ Database Schemas ### `sotce-pages` ```js { _id, user: "auth0|sub", words: "...", when: Date, state: "draft"|"published"|"crumpled", questionId?: "...", isQA?: true, updatedAt?: Date } ``` ### `sotce-asks` ```js { _id, user, handle?, question, when: Date, state: "pending"|"answered"|"rejected", answer?, answeredBy?, answeredAt?: ISO, draftAnswer?, draftStartedAt?: ISO, draftLastEditedAt?: ISO, rejectedBy?, rejectedAt?: ISO } ``` ### `sotce-touches` ```js { _id, user, page: ObjectId, when: Date } // Unique index on (user, page) ``` ### `chat-sotce` ```js { text, user, from: "@handle", when: Date, count: N } ``` ### `@handles` ```js { _id: "sotce-{sub}", handle: "amelia" } // Shared across AC + sotce tenants ``` --- ## 🎭 Theme System CSS custom properties in `:root` (light) and `@media (prefers-color-scheme: dark)`. Canvas reads them via `getThemeColors()` β†’ `getComputedStyle()`. **Key vars:** `--card-background`, `--card-text`, `--card-border`, `--card-ear`, `--question-card-background`, `--garden-background`, `--chat-background`, `--line-height: 1.76em`, `--page-font: "Helvetica"`, `--max-lines: 19` **Light:** Pink/cream paper. **Dark:** Warm brown/sepia. --- ## ⚑ Redis Cache Key `sotce-subscribed` β†’ hash field `{user.sub}` β†’ JSON `{status, current_period_end}`. Set after Stripe lookup, auto-invalidates on expiry or cancellation. --- ## πŸ§‘β€πŸ’» Dev Mode When `NETLIFY_DEV` is truthy: - πŸ’³ Test Stripe keys - πŸ“‚ Local asset path `/assets/sotce-net/` - πŸ”„ WebSocket live reload (`wss://localhost:8889`) - πŸ›£οΈ URL prefix `/sotce-net` (e.g., `/sotce-net/chat`) - πŸ’Ύ Session preserved via `?session-sotce=retrieve` Run: `npm run aesthetic` (full stack) or `npm run site` (client only). --- ## πŸ”§ Common Dev Tasks **βž• New route:** Add path to the `if` condition ~line 270, update `title` assignment, add client routing in `updatePath()`. **βž• New API endpoint:** Add `else if` clause after ~line 9238. Use `authorize(event.headers, "sotce")` for auth, `respond(status, body)` to return. **🎨 Canvas changes:** Start at ~line 6367 (`USE_CANVAS_GARDEN`). Key fns: `renderPage()`, `renderCardBack()`, `wrapText()`, `getThemeColors()`. Animation in `loop()`. **πŸ’³ Subscription logic:** `subscribed(user)` checks Redis cache β†’ Stripe. Admin emails bypass. Cache TTL from `current_period_end`. **πŸ’¬ Chat:** Connects to `sotce` room via `new Chat(dev, undefined, disconnectCallback)`. Messages via session server WebSocket. History on `connected` event. --- ## πŸ—ΊοΈ Source Map β€” `sotce-net.mjs` ### πŸ“¦ Server-Side Preamble (1–265) | Lines | What | |-------|------| | 1–62 | πŸ“‹ TODO list | | 63–98 | ♻️ Env, imports (Stripe, DB, auth, KV) | | 106 | πŸš€ `export const handler` | | 129–226 | πŸ’³ `subscribed(user)` β€” Stripe + Redis cache check | | 230–264 | πŸ“Š `getActiveSubscriptionCount()` | ### 🎨 CSS (319–3209) | Lines | What | |-------|------| | 319–510 | πŸŒ— Theme variables (light `:root` + dark `@media`) | | 511–860 | πŸŒ‘ Dark mode DOM overrides | | 860–1010 | πŸ“ Base layout (html, body, scroll, print) | | 1010–1280 | πŸ“„ Page/card rendering (font size, 4:5 ratio) | | 1280–1620 | ❓ Ask editor styles | | 1620–1870 | πŸ“ Respond editor styles | | 1870–2100 | 🌻 Garden layout (binding, FYP scroll-snap) | | 2100–2500 | πŸ• Ear fold, backpage, hover states | | 2500–2770 | πŸ”˜ Buttons, nav, cookie menu | | 2770–3100 | πŸšͺ Gate, spinner, chat, veil, tooltips | ### πŸ’¬ Client JS β€” Chat (3227–4140) | Lines | What | |-------|------| | 3227–3280 | πŸ—ΊοΈ Env, `updatePath()`, platform detection | | 3280–3310 | πŸ’¬ Chat init, `connect("sotce")` | | 3312–3420 | πŸ–±οΈ Chat DOM, click/hover handlers, page previews | | 3420–3550 | πŸ”§ `chatScrollToBottom()`, `linkifyText()`, `chatAddMessage()`, fading | | 3555–3740 | ⌨️ Chat input bar, autocomplete system | | 3740–3950 | 🎹 Monaco Editor β€” language, themes, keybindings, mobile | | 3951–4000 | πŸ“€ `chatSend()` | | 4011–4140 | πŸ“₯ `chat.system.receiver` β€” event handlers | ### πŸšͺ Gate & 🌻 Garden (4141–5040) | Lines | What | |-------|------| | 4141–4245 | 🏷️ URL param flags, auth UI setup | | 4246–4810 | πŸšͺ `gate(status, user, subscription)` β€” full gate builder | | 4810–4920 | 🌻 `garden()` β€” top bar, chat button, ask/respond buttons | | 4920–5040 | πŸ”˜ Ask + Respond button handlers | ### ✍️ Editors (5045–6290) | Lines | What | |-------|------| | 5045–5405 | ❓ `openAskEditor()` β€” 5-line question form, "my questions" | | 5403–5775 | πŸ“ `openRespondEditor()` β€” admin Q&A, prev/next, draft save | | 5831–6290 | ✍️ `compose()` / Write a Page β€” 19-line editor, keep/crumple/publish | ### πŸ“¦ Feed & Cache (6295–6365) | Lines | What | |-------|------| | 6295–6365 | πŸ”€ Feed construction (pages + questions β†’ sorted feed), IndexedDB cache | ### 🎨 Canvas Garden Renderer (6367–7470) | Lines | What | |-------|------| | 6367 | 🚩 `USE_CANVAS_GARDEN = true` | | 6370–6455 | πŸ“Š State: page index, transitions, drag, flip, card dims | | 6457–6485 | 🎭 `getThemeColors()` β€” CSS vars β†’ canvas colors | | 6487–6520 | πŸ“ `resizeCanvas()` β€” card sizing (4:5, centered) | | 6525–6555 | πŸ“₯ `fetchPage(idx)` β€” IndexedDB β†’ server fallback | | 6560–6590 | πŸ“ `wrapText()` β€” canvas word wrap | | 6590–6810 | πŸ“„ `renderPage()` β€” diary card OR Q&A card | | 6813–6928 | πŸ”™ `renderCardBack()` β€” touch info, @handle hit boxes | | 6928–7090 | πŸ–ΌοΈ `render()` β€” main frame (flip, transition, drag) | | 7090–7095 | πŸ”„ `loop()` β€” `requestAnimationFrame` loop | | 7112–7170 | πŸ‘† Pointer events (drag navigation) | | 7171–7230 | βŒ¨οΈπŸ–±οΈ Keyboard + wheel navigation | | 7233–7470 | πŸ–±οΈπŸ‘† Click, mousemove, touch (ear flip, links, handles) | ### πŸ“œ DOM Fallback Garden (7517–8530) | Lines | What | |-------|------| | 7517–8530 | πŸ“œ DOM-based rendering (fallback when canvas off): scroll-snap FYP, `renderPageContent()`, drag, ear/backpage DOM | ### πŸ” Auth Flow & Session (8532–9230) | Lines | What | |-------|------| | 8534–8600 | πŸ” Auth0 client init | | 8600–8650 | πŸ’Ύ Session restore (localStorage / URL params) | | 8650–9160 | πŸ”€ Main auth flow β†’ gate/garden routing | | 8820–8905 | πŸ”§ `veil()`, `unveil()`, `flash()`, `login()`, `signup()`, `subscribe()` | | 8905–9015 | πŸ’Ύ IndexedDB cache fns (`getCachedPage`, `setCachedPage`, `getCacheMeta`, `clearPageCache`) | | 9017–9090 | πŸ’³ Client `subscribed()`, `cancel()` | | 9092–9170 | πŸšͺ `logout()`, `resend()`, `aesthetic()` | | 9171–9228 | πŸ“‘ `userRequest(method, endpoint, body)` β€” authenticated API caller | ### πŸ“‘ REST API Endpoints (9238–10125) | Lines | What | |-------|------| | 9238 | πŸ“Š `GET /subscribers` | | 9246 | πŸ’³ `POST /subscribe` (Stripe checkout + dup guard) | | 9321 | πŸ”‘ `POST /subscribed` (sub check + pages/Qs fetch) | | 9476 | ❌ `POST /cancel` | | 9482 | ✍️ `POST /write-a-page` (draft CRUD + publish) | | 9627 | πŸ‘† `POST /touch-a-page` | | 9698 | πŸ—‘οΈ `POST /delete-account` | | 9785 | ❓ `POST /ask` | | 9823 | πŸ“‹ `GET /asks` | | 9839 | πŸ“‹ `GET /asks/pending` | | 9863 | πŸ“ `POST /ask/:id/respond` | | 9926 | πŸ’Ύ `POST /ask/:id/save-draft` | | 9964 | 🚫 `POST /ask/:id/reject` | | 10003 | 🧹 `DELETE /asks/clear-all` | | 10017 | πŸ—‘οΈ `DELETE /ask/:id` | | 10047 | πŸ“œ `GET /privacy-policy` | ### 🧩 Module-Level (10127–10271) | Lines | What | |-------|------| | 10127 | πŸ’³ `cancelSubscription()` β€” Stripe cancel + Redis clear | | 10195 | πŸ“Š `analyticsScript` β€” GA tag | | 10214 | πŸ”„ `reloadScript` β€” dev WebSocket live reload | --- ## πŸ“… Date Convention Comment timestamps: `YY.MM.DD.HH.MM` (e.g., `24.06.13.06.38` = Jun 13 2024 6:38 AM). --- ## πŸ“‹ Open TODOs πŸ”Š Sound (sine clicks/beeps) Β· πŸ“Έ Pictures in pages Β· πŸ”” Chat notifications Β· πŸ“§ Email blasts for new pages Β· 🧘 Meditation timer Β· πŸ“Ÿ Multi-user page feed Β· β™Ώ Accessibility (tab index, zoom, relational scroll) Β· πŸ“° Snippet endpoint (latest page on login)