Skip to content

Reorganize into src/ + public/ layout; dignified empty states + Inbox integrity - #5

Merged
Seanathon merged 3 commits into
mainfrom
refactor/src-public-layout
Jun 27, 2026
Merged

Reorganize into src/ + public/ layout; dignified empty states + Inbox integrity#5
Seanathon merged 3 commits into
mainfrom
refactor/src-public-layout

Conversation

@Seanathon

Copy link
Copy Markdown
Owner

Summary

Two things, in three commits:

  1. Reorganize the repo into a conventional src/ + public/ layout. The project grew from an HTML/JS prototype, leaving ~15 loose backend .ts files at the root next to frontend assets, config JSON, and scattered docs. This moves all server + browser-shipped code under src/, the static shell under public/, and consolidates narrative docs under docs/ — with no runtime behavior change.
  2. Make empty/degraded board states first-class, and protect the Inbox — so the app never white-screens or dead-ends, and composed boards arrive with their own taste.

Commits

  • chore: capture pre-existing working-tree edits (package metadata, typecheck script, tsconfig allowJs/exclude, LICENSE) as their own commit.
  • refactor(repo): the src/ + public/ move. The whole backend tree moved uniformly, so every relative import stayed valid; only the non-relative couplings were touched (static root → ../public, __dirname data reads re-anchored at the repo root, child-process cwd, the test script → globs, Dockerfile/systemd/README run paths, tsconfig exclude).
  • feat: dignified empty/degraded states + Inbox integrity (details below).

Feature detail

  • Inbox integrity: DELETE /api/boards/inbox now returns 409 instead of cascading, so the app can never reach zero boards.
  • Frontend resilience: load() no longer throws past renderSwitcher when /api/collections fails or is empty. It always renders the switcher (incl. the + affordance) plus a board-level fallback — a calm "Can't reach the server" + Retry, or a "No boards yet" invite whose "Describe a board" CTA opens the existing composer (progressive disclosure). The filter/sort toolbar hides in these states; hides with no active board.
  • Composer-written empty-state copy: an optional empty_state {head, body} on the board descriptor (backward-compatible, like archive_on_promote). The compose-board prompt asks for it in the board's voice; guardrails trim/cap and drop it if blank; emptyVoice() renders it for composed boards. A "Mood board" or "Videos" board now gets bespoke copy instead of the generic "This board is ready."

Verification

  • npm run typecheck clean.
  • 524 tests pass across 62 files (test script now globs src/** + extension/**; baseline was 518 + 6 new tests).
  • Booted the server and curled every served URL (200 + correct content-type), incl. the two src/-served browser modules.
  • Browser-verified: Inbox delete → 409; the degraded state + Retry recovery; and the no-boards "Describe a board" → composer.
  • One honest gap: the composer emitting good empty-state copy end-to-end needs a configured LLM provider; the schema, guardrails, and render path are unit-tested, and the prompt change is by inspection.

Notes for review

  • The chore: commit folds in pre-existing local edits that were uncommitted before this work.
  • Backend internals of the already-organized subdirs (db/, capture/, enrichment/, …) were intentionally not restructured — only relocated under src/.

🤖 Generated with Claude Code

Seanathon and others added 3 commits June 26, 2026 19:13
…LICENSE

Pre-existing working-tree changes captured as their own commit before the
directory reorganization: package.json metadata + typecheck script,
@types/better-sqlite3, tsconfig allowJs/checkJs + exclude list, LICENSE,
and assorted test/source tweaks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The repo grew from an HTML/JS prototype, leaving ~15 loose backend .ts files
at the root next to frontend assets, config JSON, and scattered docs. Move to a
conventional layout so server code, browser code, and data are distinguishable
at a glance. No runtime behavior change.

- src/      all server + browser-shipped code (loose root .ts + api/ capture/
            capture-clients/ db/ descriptor/ enrichment/ llm/ skills/, intact)
- public/   static shell served as-is (index.html, sw.js, manifest, icons)
- docs/     stories/ -> docs/stories, _planning_documents/ -> docs/planning

The whole backend tree moved uniformly, so every relative import stayed valid.
Only the non-relative couplings were touched:
- data reads anchored at repo root (collections.json / taxonomy.json /
  bookmarks.json / library.json) via path.join(__dirname, "..", ...) so existing
  local data is preserved in place
- server.ts static root -> ../public; two explicit routes serve the browser JS
  that stays in src/ (/collections-ui.js, /descriptor/render-map.js)
- spawn cwd for add.ts kept at repo root (DATA_DIR is cwd-relative)
- package.json test script switched to a glob (src/**, extension/**); run-mode
  paths (Dockerfile CMD, systemd units, README) -> src/server.ts
- tsconfig exclude sw.js -> public/sw.js

Verified: tsc --noEmit clean; 518 tests across 62 files pass (== baseline);
server boots and every served URL returns 200 with the right content-type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three related touches so the board never dead-ends and composed boards arrive
with taste (PRODUCT.md: 'degrade with dignity', 'empty states are first-class
designed moments').

A. Inbox is now a protected system board — DELETE /api/boards/inbox returns 409
   instead of cascading, so the app can never reach zero boards.

B. Frontend resilience: load() no longer white-screens when /api/collections
   fails or returns empty. It always renders the switcher chrome (incl. the +
   affordance) and a board-level fallback (renderBoardsFallback): a calm
   'Can't reach the server' + Retry, or a 'No boards yet' invite whose
   'Describe a board' CTA opens the existing composer (progressive disclosure).
   The filter/sort toolbar hides in these states; '⚙' hides with no active board.

C. The composer now writes per-board empty-state copy. Added an optional
   empty_state {head, body} to the board descriptor (backward-compatible, like
   archive_on_promote); the compose-board prompt asks for it in the board's
   voice; guardrails trim/cap and drop it if blank; emptyVoice() renders it for
   composed boards. A 'Mood board' or 'Videos' board now gets a bespoke empty
   state instead of the generic 'This board is ready.'

Verified: tsc clean; 524 tests pass (+6); in-browser — Inbox delete 409,
degraded state + Retry recovery, and no-boards → composer all confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Seanathon
Seanathon merged commit dbfb82b into main Jun 27, 2026
2 checks passed
@Seanathon
Seanathon deleted the refactor/src-public-layout branch June 27, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant