7-stack hierarchy (after multi-session debate exploring PARA, Johnny.Decimal, BASB/CODE, LYT/MOC, Streams+Lenses). Final shape: hierarchy as spine + Mnemosyne tags as supplementary + PARA-lite Active/Reference/Archive state filter (UI toggle, not a stack).
Top-level stacks (sidebar order):
Sub-structure per stack:
FRAMEWORKS
├── Methodology (season-framework, content-tree-concept, conceptual frameworks)
├── SOPs (step-by-step procedures — onboarding, deep-dive call guide)
├── Playbooks (channel-specific applied — IG playbooks)
└── Templates (blank starting forms — brand-guide-template, plan-de-match-template)
BUILDS
├── Citadel · Library of Alexandria · Forge · Client System · Kamvas · Bema
└── (slots) Mouseion · Delphi · Gymnasio (cross-link to fitness-app/PROJECT_MEMORY)
HOUSES (internal: Accounts)
├── Prospects (pitched, not signed yet — graduates to Clients on win)
├── Clients (LDH · Cody · Wolf Pack · Nicolas · Kevin · spa-mobile legacy)
├── Collabs (joint productions with non-clients)
├── Self 🔒 (Personal Brand · Personal Creative — FUTURE: password-protected)
├── Partners (SpeakUp · Hoski · Argo — their own brand work, not their clients)
└── One-offs (small jobs without full pipeline)
LAB
├── Experiments (content/format trials, no destination yet)
├── Prototypes (sandbox builds, pre-Build code)
├── Studies (learning intake + accumulated knowledge — by topic)
│ ├── AI & Tooling · Marketing & Content · Business & Strategy
│ ├── Design & Aesthetics · Engineering · Psychology & Behavior
│ └── + open slot
└── Drafts (writing in development)
STREAMS
├── Voice Notes · Journal · Swipe File · Observations
OPERATIONS
├── Admin (contracts · invoicing · vendor research)
├── Stack (tools · configs · API references)
└── Rituals (weekly reviews · quarterly retros · annual planning)
REFERENCE
├── About Jason · Network · Vocabulary · Accumulated
Internal House sub-shape (applies uniformly to every House sub-type — Clients/Collabs/Self/Partners):
Brand · Strategy · Research · Deliverables · Notes
Naming rationale:
Key distinctions resolved:
Three redundancies caught in audit + resolutions:
project_ldh.md (auto-memory) folds into clients/les-dames-des-hypotheques/notes.md — single rolling log home.thumbnail_two_pass_process.md retyped from project to methodology (it's a Framework).Future flag — Self protected area:
Houses > Self should eventually be password-protected. Personal brand + personal creative content (photography, music, bouldering, etc.) sits behind a gate. Design pass needed. Phase 3+.
Prospects sub-type (added 2026-05-04 wrap):
Houses > Prospects is the home for pitched-but-not-signed entities. Same internal sub-shape as other Houses (Brand · Strategy · Research · Deliverables · Notes), though early-stage prospects often have only Deliverables (the pitch deck) until they sign. On win → folder migrates to Houses > Clients. On loss → archived (state filter). Resolves Pandora's Box 3300eed7-... (Bema deck save destinations) — Bema's provisional _pitches/{slug}/ becomes prospects/{slug}/ at workspace root, mirroring clients/ conventions. Migration is a one-shot when Bema next runs against any prospect deck saved under the old path.
Wiring tasks (2D) — SHIPPED 2026-05-04:
library-sources.json classification rules → 7-stack mapping with subcategories + house_section (catch-all rules added so client files default to Houses/Clients without Mnemosyne fallback)src/mnemosyne/prompt.js — 7-name stacksCategories, subcategory in output schema, expanded workspace list (added library, kamvas, bema)project flipped from exclude_types → include_types, project_*.md now route to Builds with per-build subcategory?sub= param on /stacks/:category (supports _unsorted literal). /stacks returns nested structure: each stack has subs[] array with name/count/lastAdded in registry order?sub=<name>. StackDetailView reads sub from URL and shows All / sub pillsPlus: DB schema added subcategory + house_section columns (idempotent ALTER), validation/indexer/bootstrap pass them through, /integrity/confirm accepts subcategory override, capture pipeline now writes library_category + subcategory to voice-note frontmatter on routing.
Context arrived from a separate strategy session and takes priority over the rest of the Phase 2 backlog. Original message: Studio Setup is a first-class entity in the Library, not a side doc. Categories: solo talking head · dual talking head · dual podcast · 3-person podcast (extensible). Each setup has variations + per-client color overrides. Shady (studio team alongside Jason and Charles) needs full Library access — read AND upload, ideally drag/drop into a setup template.
Resurfacing — proposal locked pending decisions:
_ops/studio-setups/ next to _ops/methodology/type: studio_setup with fields: slug, name, owner, variations[], client_overrides{}, purpose_tags[]studio-setups/**/*.md → Frameworks > Setups (one-line addition to library-sources.json)setup: <slug> field; reading view renders "uses setup: X →" header. Reverse lookup (briefs-using-setup-X) is Phase 2.5 follow-on.Permissions — phased, deferred enforcement:
owner frontmatter convention — documented but not enforced. New generic upload endpoint + dropzone UI on the Setups page (or scp-via-Tailscale if UI is deferred).Tailscale-User-Login header. Write middleware enforces frontmatter owner against requesting user OR a workspace admin env var. Reads stay open to all authenticated tailnet users.Decisions blocking next session start: see "Decisions for next session" at the bottom of this file.
All five must-do/stretch items from the pickup brief landed in one session.
_ops/studio-setups/ created with README + 4 stub files (solo-talking-head, dual-talking-head, dual-podcast, 3-person-podcast), each carrying the locked type: studio_setup frontmatter (slug, name, owner, variations, client_overrides, purpose_tags). Classification rule studio-setups/**/*.md → Frameworks > Setups added at top of the ops source's classification_rules in library-sources.json. Setups appended to the Frameworks subs registry. Bootstrap re-ran → 113 files indexed (was 102). API confirms Frameworks > Setups (5)./stacks endpoint now surfaces all declared subs (count 0 when empty), so Houses shows Prospects(0)/Clients(48)/Collabs(0)/Self(0)/Partners(0)/One-offs(0); Frameworks shows Methodology/SOPs/Playbooks/Templates/Setups; Lab/Operations/Reference all expose their declared sub shape. StackDetailView rewritten as a folder grid by default (each sub a .stack-card reusing existing CSS) with a Folders / See flat list toggle. Drill-down sets ?sub=<name>; breadcrumb back-link returns to the folder grid. Stacks with no declared subs (e.g. uncategorized) skip folder mode and render the flat list as before.FIXED_COLORS map for all 7 stacks (sage / terracotta / slate-blue / plum / amber / slate-teal / wine — earthy hues spanning the wheel, all distinct from each other at the sidebar dot scale). hashCategoryColor hue range widened from 140-340 to 0-360 so warm tones return for any legacy or Mnemosyne-drift category that falls through to the hash._ops/studio-setups/README.md now spells out the owner convention (jason | shady | charles), a concrete scp-via-Tailscale upload path, and the Phase 3 spec for Tailscale-User-Login middleware enforcement. Inviting Shady + Charles to the tailnet is admin work that requires the Tailscale admin console — flagged for Jason. UI dropzone deferred to Phase 2.5.ReadingView now reads fm.setup from frontmatter and renders an uppercase mono kicker above the title (USES SETUP: <Display Name> →). Click navigates to /file?id=ops:studio-setups/<slug>.md so the setup file opens in the same reading surface. Slug → display name is title-cased client-side. Reverse lookup (briefs-using-setup-X) still Phase 2.5.Five scoped commits on library-app/main:
19a2c6e — Wire Studio Setups subcategory + surface empty subs as foldersed1baa2 — Subcategories as folders: drill-down navigation in StackDetailView482f245 — Add explicit color palette for the 7 stacks0ea73cf — Render uses-setup cross-link in ReadingView when fm.setup is present117a6a0 — Rebuild frontend bundleServer restarted via launchctl stop/start com.library.server so the registry edit + new /stacks shape are live.
Distribution after re-bootstrap: Frameworks 31 (Methodology 25 · SOPs 1 · Setups 5; Playbooks/Templates surfaced empty) · Builds 23 (LoA 7 · Forge 6 · Client System 6 · Kamvas 2 · Bema 1 · Workspace 1; Mouseion/Delphi/Gymnasio/Citadel surfaced empty) · Houses 48 (Clients 48; Prospects/Collabs/Self/Partners/One-offs surfaced empty) · Lab 0 · Streams 6 (Voice Notes 4 · Journal 2; Swipe File/Observations surfaced empty) · Operations 1 · Reference 3.
Surfaced near session end. The Provenance UI today only exposes skip/approve for pending routes and dismiss/confirm → <suggested> for classification review. The backend already supports modify/override — both api.modifyRoute(id, destination) (POST /pending-routes/:id/modify) and api.confirmClassification(docId, category, subcategory) accept overrides — but neither has a UI affordance. When Mnemosyne's suggested path is wrong, the user has no in-UI path to reroute; they have to skip then manually move.
Goal for next session: add a "reroute" or "modify destination" action to both the pending-route detail panel and the classification-review detail panel. Picker UX needs a quick way to choose stack + subcategory for library routes (typeahead or two-step picker). Mobile Provenance modal port (Phase 2 backlog item 3) becomes the natural moment to also redesign the action row.
All four must-do/stretch items from this session's pickup brief landed in three scoped commits on library-app/main. Backend was extended first to make the override actually take effect, then the frontend grew the picker UI and the mobile drawer.
POST /pending-routes/:id/modify now accepts { library_category, subcategory } alongside { destination }. For library destinations it persists the override on the pending-route record AND patches the voice note's .md frontmatter (then invalidates the enriched-files cache) so the override propagates without waiting for re-bootstrap. api.modifyRoute(id, destination, { library_category, subcategory }) extended client-side. Pending-route detail panel renders a stack <select> + subcategory <select> for library routes (subs filtered to the chosen stack's declared list, pulled from /api/library/stacks) or a workspace <select> for forge_queue routes (eight workspace tags hardcoded — mirrors src/mnemosyne/prompt.js). Pre-filled from the suggestion. Approve button label flips to reroute & approve when values differ; otherwise plain approve.<select> pre-filled from suggested_category / suggested_subcategory. Confirm button flips to reclassify & confirm when changed; calls confirmClassification(docId, category, subcategory) with overrides. The 42 leftover classification_needs_review rows (mostly Mnemosyne drift like "Notes" / "Strategy") are now triagable in-app.dismiss button renamed to skip on the integrity panel for consistency with pending-route side. The picker now provides the reroute-instead-of-dismiss path, so dismiss is no longer the only escape. Records still persist on disk: pending-routes.jsonl keeps status='skipped' entries, classification_needs_review keeps reviewed=1 rows. Soft archive by default — UI hides them but the data isn't deleted. A "review history" surface to browse them is Phase 2.5 polish, not blocking.src/mnemosyne/trail.js) now stamps library_category + subcategory into the routing log frontmatter; /routing-log endpoint surfaces them on each entry; formatRoutingLogDestination() helper renders → library : <stack> / <sub> for library entries and → forge / <workspace> for forge_queue. Existing trail files (pre-this-commit) lack the fields and gracefully fall back to → library — no migration needed.Three scoped commits on library-app/main:
c3e8763 — Extend pending-route /modify to accept category overrides + persist to voice note frontmatter2365537 — Provenance reroute UI: picker + mobile drawer + dismiss-as-skip0e9c761 — Rebuild frontend bundleServer restarted via launchctl stop/start com.library.server. /api/library/stacks returns the 7-stack registry (Frameworks 31 · Builds 23 · Houses 48 · Lab 0 · Streams 6 · Operations 1 · Reference 3); /api/library/integrity returns 42 review rows (unchanged from session 2 wrap — triage UI now available, but actual triage is a Jason-at-keyboard task).
Backend gap closed: modify previously updated only the pending-route record; the voice note frontmatter retained the original Mnemosyne-suggested category. Now modify also patches the .md file, so the override propagates immediately.
Full architecture locked in one Daedalus pass. Decision pad lives at _ops/library-of-alexandria/EDITING_AND_COLLABORATION_DECISIONS.md (replaces the question list in EDITING_AND_COLLABORATION_SCOPE.md). Highlights:
?mode=edit for shareabilitylibrary/.search/. W3C Web Annotation Data Model anchor (TextQuoteSelector + TextPositionSelector with fuzzy reposition fallback). Sidebar persists across modes (right rail desktop / bottom drawer mobile).collaborators: frontmatter block. Folder-default cascade via new default_collaborators_by_path in library-sources.json. Phase 3 middleware (per studio-setups/README.md) extends from owner == requester to the 4-role check. Phase 3 spec NOT redesigned, only extended.edit permission via share token (body edits require Tailscale identity).lander.tabs[] with per-tab permission_override + access: internal_only.edit_log SQLite table (separate from routing log). Patches as unified-diff files in library/logs/edits/{docId}/{ULID}.patch. History tab in sidebar with timeline + diff expansion + "Restore this version" button.Full Phase A scope landed in one session. All 8 build tasks completed.
Backend (library.js + db.js):
doc_locks + edit_log SQLite tables (idempotent, auto-created on server start)POST /api/library/file/:id/lock — soft-lock with 409 contention response (holder + expires_at)DELETE /api/library/file/:id/lock — release lockPOST /api/library/file/:id/heartbeat — extend lock TTL by 5 min (60s client interval)PUT /api/library/file/:id — save: mtime conflict check → createPatch() unified diff → .patch + .snap files in library/logs/edits/{safeDocId}/ → edit_log row → cache invalidated → returns new mtimeGET /api/library/file/:id/history — edit log entries (action=edit, 50 max, DESC)GET /api/library/file/:id/history/:patchId — returns { diff, snap } (diff for display, snap is before-snapshot for restore)GET /api/library/file/:id now also returns rawContent (full file text) + mtimeNew packages: diff (server — unified diff generation), codemirror + @codemirror/lang-markdown (client — editor)
Frontend:
client/src/lib/draft.js — IndexedDB draft utility (saveDraft/loadDraft/clearDraft keyed by docId)client/src/lib/api.js — lockFile, heartbeatFile, unlockFile, saveFile, fileHistory, historyPatch + put/del fetch helpersclient/src/views/ReadingView.jsx — full rewrite: View/Edit mode toggle (?mode=edit URL param), CodeMirror 6 with parchment theme + markdown + line wrapping, lock on entry + 60s heartbeat + release on exit, IndexedDB draft loaded on entry, 2s debounced autosave to IndexedDB, ⌘S / Save button → PUT → mtime updated → draft cleared, 409 conflict UI ("Keep mine" / "Reload"), History sidebar (right rail, 280px) with timeline + diff expansion + "Restore this version"client/src/styles/app.css — edit mode controls, save/unsaved indicators, conflict banners, history sidebar, CodeMirror container, diff block rendererActor identity in Phase A: Tailscale-User-Login header → env LIBRARY_ACTOR → 'jason'. Phase B replaces with real middleware (now done).
One scoped commit on library-app/main: Phase A solo edit-in-place — all 8 components.
All 4 Phase B deliverables landed in one session. Commit 960376c on library-app/main.
New src/auth/permissions.js:
ROLES ladder: ['read', 'comment', 'suggest', 'edit']hasRole(userRole, minRole) — ladder comparisonresolveDocRole(frontmatter, relPath, sourceId, user) — cascade: admin list → owner → per-doc collaborators: frontmatter → folder defaults → 'read'resolveFolderDefaults(relPath, sourceId) — walks default_collaborators_by_path in library-sources.json with minimatchrequireDocRole(minRole) — Express middleware factory; reads file frontmatter, resolves role, returns 403 if insufficient; passes through on missing files (route handles 404)LIBRARY_ADMINS env var (comma-separated), falls back to LIBRARY_ACTOR or 'jason'library-sources.json — default_collaborators_by_path added to:
clients source: les-dames-des-hypotheques/** → { shady: 'edit', charles: 'comment' }ops source: studio-setups/** → { shady: 'edit', charles: 'comment' }src/routes/library.js:
requireDocRole, resolveDocRole, hasRoleGET /file/:id now returns actor, userRole, canEdit (frontend-visible)POST /file/:id/lock, DELETE /file/:id/lock, POST /file/:id/heartbeat, PUT /file/:id — all now guarded by requireDocRole('edit')client/src/views/ReadingView.jsx:
doc?.canEdit !== falsedoc.actor is presentSmoke tests passed:
jason → edit on all docs (admin)shady → 403 on non-LDH doc lock attempt (resolves to 'read')shady → edit on LDH docs (folder default)charles → comment / canEdit: false on LDH docs (folder default)Items 1–5 from session 2 + session 3 shipped (Studio Setups + folder navigation + 7-stack palette + setup cross-link + Provenance reroute UI + mobile drawer + dismiss-as-skip + routing log granularity). Editing+collab architecture locked in session 4 (2026-05-06). What's left:
cat-ing the markdown. Today the Library has the file, but discovery friction is too high. Probable shape: a compact ?q= cross-session search URL surface (already exists at /api/library/search + /search view) — worth checking PWA performance on phone. Touches the same surface as project_library_html_rendering.md (HTML rendering in-app). Note: lander pattern canonization (previously a separate Phase 2 item) folds into Phase D scope per the decision pad. Public share links (previously a separate Phase 2 BUILD_PHASES item) also fold into Phase D.project_library_html_rendering.md Phase 2 — iframe-based rendering of .html files in-app. Same surface as item 5.pending-routes.jsonl status='skipped'; classification_needs_review reviewed=1) — just no UI today. Low priority; the picker now removes most of the need.Commit 104e385 on library-app/main.
annotations SQLite table — schema per §3.1 of decision doc. kind CHECK('comment','highlight','suggestion'). W3C anchor fields: anchor_quote, anchor_prefix, anchor_suffix, anchor_offset. Two indexes: by doc_id+resolved+created, by author+created. Idempotent CREATE TABLE IF NOT EXISTS in src/db.js.GET /api/library/file/:id/annotations — lists all unresolved annotations for a doc, ordered by created ASC.POST /api/library/file/:id/annotations — creates annotation; guarded by requireDocRole('comment'). Body: kind, body, anchor_quote (required), plus optional anchor fields.api.annotations(id) + api.createAnnotation(id, body) in client/src/lib/api.js.requestAnimationFrame + document.createTreeWalker + Range.getBoundingClientRect() to compute Y positions scroll-independently; renders .annot-gutter-mark dots absolutely positioned in .prose-annotated wrapper.mouseup on prose container → floating fixed-position tooltip with textarea → POST → reload annotations → markers re-render. Dismissed on Escape or cancel.GET returns count:0 on fresh doc; POST persists row with correct author (jason); GET after POST returns the annotation.Architectural notes:
findAnnotationY() formula: rangeRect.top - containerRect.top — scroll-independent because both are viewport-relative and scroll cancels. Marker at position: absolute; top: y inside position: relative wrapper stays correct regardless of scroll state at compute time.left: -18px — safe within .reading-article's left padding (24px mobile, 40px desktop) so not clipped by the overflow container.dangerouslySetInnerHTML prose path is wired. Session 2 can extend.01KR2EJD5W92NZ6WX9JQENTQZ3, anchor_quote "annotations + highlights".Phase C session 2 — SHIPPED 2026-05-08, commit 919ab68:
injectAnnotationHighlights() — post-processes rendered HTML string to inject <mark class="annot-inline"> spans. Processes annotations largest-offset-first to avoid index drift.PATCH /file/:id/annotations/:annotId — sets resolved=1. api.resolveAnnotation() added. Resolved annotations hidden from GET list immediately.Phase C session 3 — SHIPPED 2026-05-08, commit a8b5637:
kind='suggestion' with suggestion_replacement. On create, sidebar auto-opens to Suggestions tab..replace(anchor_quote, suggestion_replacement) → save → resolve annotation → unlock. Reject = PATCH resolved=1.api.patch() helper added. api.resolveAnnotation() reuses PATCH.Phase C session 4 + Phase D — SHIPPED 2026-05-08, commit 42ce7cb:
.comment-thread wrapper; replies (parent_id set) indented under parent with .comment-reply. Each top-level comment has a "Reply" link that toggles an inline textarea. Reply POST sets parent_id + inherits anchor_quote. No separate endpoint needed — same POST /annotations with parent_id.GET /export/:id?format=md&withComments=1 — same as md export but fetches all unresolved annotations from SQLite and appends as ## Comments footnotes (author, date, quoted excerpt, body). Filename gains -with-comments suffix. Marginalia export section shows "↓ Markdown + comments" button only when annotations.length > 0.share_tokens SQLite table (token=ULID PK, doc_id, permission, created_by, created_at, expires_at, revoked_at, label, last_accessed_at, access_count). Idempotent CREATE TABLE IF NOT EXISTS in src/db.js.POST /api/library/file/:id/share (create, requireDocRole comment+; logs to edit_log as share_created) · GET /api/library/file/:id/shares (list active) · DELETE /api/library/file/:id/share/:token (revoke; logs as share_revoked)./api/share. GET /:token — validates token (revoked_at + expires_at), bumps access_count, returns doc + html + permission. GET /:token/annotations — returns unresolved annotations for comment+ tokens. POST /:token/annotations — creates annotation with author share-token:<token_short> (label); respects permission level (comment vs suggest)./share/:token via App.jsx.tailscale funnel --https=5175 --bg).Smoke tests passed:
POST /api/library/file/:id/share → ULID token returned ✓GET /api/share/:token → doc + html + permission returned ✓DELETE /api/library/file/:id/share/:token → {"ok":true} ✓GET /api/share/:token → {"error":"token_not_found"} ✓-with-comments.md, Content-Disposition correct ✓Additional work this session after Phase C/D:
--reading-width set inline; drag handle div on right edge of article. articleWidth state persisted to localStorage('lib_article_width'). Range: 480–1400px. handleResizeStart uses mousemove/mouseup with a ref to track current width without closure staleness.GET /api/library/audio/:id streams the audio file referenced in voice note frontmatter audio_path (relative path resolved against the .md file's directory). Byte-range aware (Content-Range header, 206 partial response) for scrubbing. ReadingView useEffect on playing state calls audioRef.current.play()/.pause() so the play button actually works.StackDetailView shows "Classify →" button below each ItemCard when category === uncategorized. Clicking opens a centered modal with stack + sub pickers. On confirm: api.confirmClassification(), item removed from list immediately.normalizeDeepMapContent() pre-transformer runs before marked.parse(). Converts 3-space-indented [timestamp] ↳ sub-item lines to - \[timestamp]` ↳ sub-itemmarkdown list items with blank lines before each list block. This fixes ALL existing deep map docs retroactively. Deep map skill prompt also updated to use- `[timestamp]`` list syntax for future generations.isExpanded() previously returned true unconditionally for activeStack, making the caret a no-op while on that stack. Added collapsed set (explicit overrides auto-expand). useEffect on activeStack change clears the new active stack from collapsed (auto-expands on navigation). Row click behavior: if already on stack root, row click toggles sub-list in place; if on a different stack or sub-page, row click navigates. Caret is now visual-only.project_library_rendering_architecture.md. The current single prose pipeline is a holding pattern. All rendering work is blocked on an architectural decision.Phase 2.5 — status (2026-05-08 session 2 — consultation surface):
project_library_rendering_architecture.md.d792eeb.https://macbook-pro-3.tail4a07f5.ts.net responding. LIBRARY_PUBLIC_URL set in .env.8a33db9. SearchView now reads ?q= on mount + syncs URL as user types. Copy-link button next to result count. PWA start_url → /search. Also fixed a pre-existing bug: search result navigation was silently broken — API returns docId (camelCase), component was reading doc_id (snake_case), clicks went nowhere. Fixed.deriveTitle() strips .html extension (was: "Carousel Co3 Outreach Audit.Html", now: "Carousel C03 Outreach Audit"). Word count zeroed for html_file on server (parseFile) and client (ReadingView docType check). Commit 8a33db9.2111211. See "Phase 3 — Publish to Vercel SHIPPED" section below.How to use the consultation surface (cross-session brief lookup):
http://localhost:5175/search?q=LDH+brief → opens Library pre-filled, results readyhttps://macbook-pro-3.tail4a07f5.ts.net/search?q=LDH+brief → same from phone/external?q= URL to clipboard for pasting into any Claude session/search (was /)Open Jason admin actions (not code work):
http://localhost:5175/provenancethecitadel.tools (2026-05-09 → 2026-05-10)Three commits on library-app/main: 2111211 (feature), f98bc9b (auto-disable Deployment Protection), b9eb037 (clean-URL aliasing on thecitadel.tools).
End-to-end working:
https://library-{slug}.thecitadel.tools auto-copied to clipboardhttps://library-memory.thecitadel.tools (HTTP 200, ~600ms load)Domain — locked decisions:
thecitadel.tools registered via Squarespace 2026-05-09 (~$14/yr)* → cname.vercel-dns.com (4hr TTL, propagates globally in ~20 min)bema-{slug}.thecitadel.tools, Forge → forge-{slug}..., Kamvas → kamvas-{slug}.... Each app sets its own VERCEL_APP_PREFIX env var.libraryofalexandria.tools (per-app domains don't scale), Argo/SpeakUp subdomains (agency-specific brand confusion).*.vercel.app aliases (user explicitly rejected as too generic), library.argodigital.ca (Argo expiry uncertainty + agency brand mismatch).Backend architecture:
vercelConfig, slugify, uploadFileToVercel, createVercelDeployment, disableProjectProtection, addDomainToProject, aliasDeployment, waitForDeploymentReady — split out so each step is testablealiasDeployment retries on transient errors (SSL cert provisioning, readyState propagation): 25 attempts × 3s{app}-{slug} taken by different doc, appends 4-char SHA256 hash of doc_idvercel_deploys SQLite table — doc_id PK, INSERT OR REPLACE keeps latest per docEnv vars in .env:
VERCEL_TOKEN (required) — Full Account scope, expires neverVERCEL_PROJECT_NAME (default: library-of-alexandria)VERCEL_BASE_DOMAIN (default: thecitadel.tools)VERCEL_APP_PREFIX (default: library)Frontend:
api.publishToVercel(id) + api.getVercelDeploy(id) in api.js.share-vercel-* familyVerified gotchas (logged for posterity):
disableProjectProtection PATCH after each deploy strips both. Idempotent, best-effort.waitForDeploymentReady)..tools TLD. Publishing during that window falls back to random Vercel URL gracefully.Problem: Library has TWO render pipelines that have already drifted:
MarkdownRenderer + normalizeDeepMapContent pre-processor + global app.css table/heading stylesrenderHtml() in src/routes/library.js (line 266) — raw marked.parse() with inline <style> block, untouched since Phase 1User noticed when comparing local doc render to published library-memory.thecitadel.tools — table polish, deep-map pre-processor, and any future MarkdownRenderer improvements don't reach the published version.
Decision (2026-05-10): DON'T do a quick CSS port. Bundle into the Daedalus rendering pass. Why: a quick port duplicates CSS in two places, and we'd redo it when the Daedalus pass converges everything onto a single render pipeline anyway. Acceptable to live with the gap for 1-2 sessions.
The right fix (proper SSR): use react-dom/server.renderToStaticMarkup() to render the same React components server-side. Rendering becomes a pure function (doc) → HTML. In-app wraps in a React component; renderHtml() calls it directly. Single source of truth — every future improvement automatically reaches Vercel. Aligns with the Google-Docs "one engine, every consumer" principle locked earlier this session.
This adds a fourth scoped item to the queued Daedalus rendering pass:
react-dom/server.renderToStaticMarkup() so Vercel publish uses the same engine. Touch points: renderHtml() in src/routes/library.js, MarkdownRenderer in client.library-app/main since 2026-05-08thecitadel.tools.classification_needs_review rows — picker UI live at /provenance. Jason-at-keyboard triage.Out of scope for next Library session (separate threads):
_ingest/ backlog cleanup — not Library code work, separate triage9713e25c-40ea-4676-8178-3f1b8f0b5196, mechanical 5-10 min task, runs from any session when AI Studio paid tier propagates_ops/studio-setups/ on disk — confirmedowner as convention. Phase 3 = real auth.User flagged during decision #4 review: the multi-tab deliverable lander pattern (Skawanotti interview shape, recently extended to an LDH next-month brief output) hasn't been canonized but is in active use. Pattern: a markdown source becomes a 4-tab HTML lander (overview / scripts / carousels / notes) when shared with the team or external collaborators.
Adjacent to but distinct from project_library_html_rendering.md (Phase 2 HTML iframe rendering) and Phase 2 backlog item 6 (Library as consultation surface). Both touch "how rendered views of canonical markdown reach humans." Worth tackling as one cohesive design pass when those land.
Specific questions to answer when canonizing:
_landers/ folder? Generated on demand?)NOT blocking for next session. File the design pass for the consultation-surface session.
Phase 0: complete. Phase 1: COMPLETE (2026-04-30). All 10 sub-systems (1A–1J) shipped. Phase 1.5: COMPLETE (2026-04-30). UI polish pass shipped same session. Bootstrap + infra: COMPLETE (2026-04-30 session 2). 86 files indexed, 8 collections active. GitHub remote live. PWA manifest added.
Next session: Collections organizational strategy (2C) + Phase 2 feature build.
library-app/ — Express server (port 5175), backend + React frontend sourcelibrary-app/client/ — Vite + React frontend source (builds to library-app/public/)library-app/public/ — built frontend served by Express at port 5175library/ — data corpus only: streams, notes, archives, logs, .search DB, .git for hourly backup_ops/library-of-alexandria/ — canonical planning docsDev workflow:
cd library-app && npm run dev (Express on 5175, --watch mode)npm run dev:client (Vite on 5176, proxies /api to 5175)npm run build → serves from Express on 5175http://100.72.220.52:5175 (Tailscale IP)launchd services (auto-start on login):
~/Library/LaunchAgents/com.library.server.plist — Library server, KeepAlive. No --watch — manually restart after code changes: launchctl stop com.library.server && launchctl start com.library.server~/Library/LaunchAgents/com.library.backup.plist — hourly git backup~/Library/LaunchAgents/com.forge.server.plist — Forge (Skill Dashboard) on port 5173Bootstrap command: npm run bootstrap (or npm run bootstrap:force to reindex everything)
npm run not node scripts/bootstrap.js directly — needs --env-file=.env for ANTHROPIC_API_KEYGitHub remote: https://github.com/jasonlucas4/library-alexandria.git — hourly launchd backup auto-pushes the data corpus (library/). .search/ DB excluded (regenerated by bootstrap).
App code git (added 2026-05-05): library-app/ is now its own local git repo. Initial commit 63994e7 on main captures Phase 1+1.5+2D shipped state. No remote yet — separate decision for whenever offsite backup is wanted. .gitignore covers .env, node_modules/, library/, .DS_Store, Icon. Commit at end of each Library session per session-hygiene ritual.
Key env vars in library-app/.env: ANTHROPIC_API_KEY, GEMINI_API_KEY, GROQ_API_KEY. All confirmed working.
Navigation:
/stacks/:category)Hall view:
categoryColor(item.category) — amber for voice notes, neutral gray for uncategorized, category color once bootstrap runsCollections (Stacks):
categoryDisplayName() normalizes voice_note → "Voice Notes" etc.Reading view:
clients/{slug}/... → "Client Name — File Type"; auto-memory prefixes stripped; voice_note uses summary first sentencesourceHint field shows original filename in meta when title was inferredProvenance:
GET /api/library/routing-log — reads library/logs/routing/*.md directly#tag chipsauto-executed (green) / staged (accent) / staged (low confidence) (muted)Forge Queue view:
| Route | Purpose |
|---|---|
GET /api/library/recent?limit=N |
All visible .md files sorted by mtime, enriched metadata, 30s cache |
GET /api/library/stacks |
Category counts, uncategorized always last |
GET /api/library/stacks/:category |
Items in a category |
GET /api/library/file/:id |
Single file with derived title + sourceHint |
GET /api/library/search |
FTS5 + semantic search |
GET /api/library/pending-routes?status= |
Staged routing decisions |
| `POST /api/library/pending-routes/:id/approve | modify |
GET /api/library/routing-log?limit=N |
ALL routing decisions from library/logs/routing/ |
GET /api/library/forge-queue?limit=N |
Items Mnemosyne sent to Forge |
GET /api/library/export/:id?format=md|html |
File export (1F render pipeline) |
GET /api/library/integrity |
Classification needs review |
POST /api/library/capture |
Voice note upload (202 fire-and-forget) |
Location: skills/transcript-summary/
skills/transcript-summary/modes/MODES.mdCost benchmark: ~$0.13–0.15 per ~46-min interview (2 passes, Sonnet). Much cheaper than Fireflies ($10–19/month). Break-even at ~65–125 transcripts/month.
npm run bootstrap not bare node — added npm script; (b) getEnrichedFiles() in library.js wasn't reading DB-stored Mnemosyne categories — fixed by loading from source_index table and merging.https://github.com/jasonlucas4/library-alexandria.git. Pushed 5 pending commits. Fixed .gitignore to exclude .search/ DB files.manifest.json + 192/512 PNG icons (gold columns on dark bg). apple-mobile-web-app-title = "Library". Status bar style set to black-translucent. Files live in client/public/ (survive Vite builds). iPhone: open http://100.72.220.52:5175 in Safari, tap Share → Add to Home Screen.Priority order (not all same session):
2A — Hall usability:
2B — Provenance:
2C — Collections organizational strategy:
+ New collection → updates Mnemosyne prompt + library-sources.json classification rules2D — Content enrichment:
2E — Export quality:
.html source files (sandboxed iframe)2F — Transcript Summary → Library tab:
skills/transcript-summary/modes/ at runtime (single source of truth confirmed)library-app/, data corpus in library/. Separate git repos.gemini-embedding-2 (3072 dim).whisper-large-v3-turbo) primary.marked for HTML (same engine client + server). PDF via browser print Phase 1; puppeteer Phase 2./stacks/:category.clients/{slug}/ → "Client Name — File Type"; auto-memory prefixes stripped; voice_note uses Mnemosyne summary.library/logs/routing/*.md directly — captures ALL decisions including auto-executed. pending-routes.jsonl only has staged decisions.library auto-executes but shows "→ library" in Provenance without a file link. Phase 2: link to reading view via voice_note_id.routing_log type files filtered from Hall and Collections (internal system records).