* refactor(notes): decompose Notes into feature-owned modules * fix(notes): avoid HTML interpolation in reminder picker * docs(notes): drop checkpoint archives, session logs and publication drafts The N0-N6 tarballs and raw pytest/node logs are not read by any script or test, and the logs carry absolute home-directory paths. Once squashed into dev they would stay in history for good. notes-pr.md and notes-issue.md were the drafts used to open #6712 and #6713, not documentation. notes-validation.md keeps the counts, the checkpoint hashes and implementation.sha256 as the record; local interpreter paths and the local Docker image name are replaced with portable placeholders. * fix(notes): keep the state store raw and stop masonry re-observing after close - state: writes built from views (filter, spread, splice) stored nested read-only views in the store, so structuredClone on a stored note threw DataCloneError. Store writes now unwrap nested views. - layout: a save that resolves after closePanel re-renders the leaving pane and re-created the masonry ResizeObserver that teardown had disposed. Skip creating it once Notes is closed. - index.js: drop 30 imported or declared names that were never used and group the imports at the top. Import order is unchanged. - Revert unrelated punctuation edits in .gitattributes and keep the header comment and docstring first in compare/vote.js and test_notes_dom_xss_helpers.py. Both fixes have a node test that fails on the previous head. * test(notes): make script rejection assertion case-insensitive Address CodeQL js/bad-tag-filter alert #1150 in test-only assertion and refresh Notes evidence manifest checksum. --------- Co-authored-by: Léo <leograndcontact@gmail.com>
14 KiB
Odysseus frontend architecture handbook
Status: F1 merged in PR #6701. The complete Notes reference is implemented as an
unpublished review diff on refactor/notes-reference-decomposition. Read its
case study, module inventory
and validation receipts. Publication and the recipient's
post-integration reproduction remain pending.
Read the ADR, assessment, validation report and Notes roadmap. The frontend program ends with the Notes reference decomposition and handoff. Calendar, Documents, Email, Chat, Skills, Settings and other later decompositions belong to the designated frontend maintainer; do not start them automatically.
Source, ownership and public APIs
frontend/src/app/ composes public feature APIs and platform ports. Each
features/<feature>/index.js is an intentional API, with explicit named exports
and a compatibility default only when existing consumers require one. Internals
live under domain/, application/, infrastructure/ or ui/ as needed. Do not
create empty directories, boilerplate controllers or a framework by convention.
Feature names app and shared are reserved.
Keep pure business rules in domain; transitions and workflows in application; HTTP/storage/browser services in infrastructure; DOM rendering and lifecycle in UI. The index composes adapters, state and views. Application receives adapters as explicit arguments or constructor ports, rather than importing concrete infrastructure. Views call application operations; infrastructure does not own business decisions. A public API returns data/operations, not mutable stores or private DOM nodes. Importing an API should define capabilities; explicit lifecycle methods activate them. Legacy side effects remain characterized until a reviewed slice safely changes their placement.
Shared code has a named, proven cross-feature responsibility. shared/domain/
holds pure primitives, shared/platform/ holds narrowly scoped platform adapters,
and shared/ui/ holds common DOM capabilities. A named stateless root leaf such
as shared/actionMenuOrder.js is also allowed and receives domain restrictions.
Do not create a central utils.js collection or move feature business rules into
shared simply to satisfy an import rule.
Mechanically checked dependency contract
| Importer | Permitted maintained-source targets |
|---|---|
| App composition | Other app code, shared code, feature index.js APIs |
| Feature index | Its own responsibility modules and shared code; other feature public indexes |
| Feature domain | Its own domain and shared domain/stateless leaves |
| Feature application | Its own application/domain, shared domain, other feature public indexes |
| Feature infrastructure | Its own infrastructure/domain, shared domain/platform |
| Feature UI | Its own UI/application/domain, shared domain/platform/UI |
| Shared domain leaf | Shared domain leaves only |
| Shared platform | Shared platform/domain only |
| Shared UI | Shared UI/platform/domain only |
No feature imports app composition. No shared code imports a feature. No external feature imports private responsibility modules. UI and infrastructure communicate with another feature through a supplied application operation/port. Maintained source dependency cycles, including literal lazy imports, fail the architecture gate. If features need bidirectional coordination, app composition injects a callback or adapter instead of creating mutual imports. Prefer this even when a one-way public index import would pass the gate.
Cross-feature events must document event name, payload, acknowledgement, owner,
subscription disposal and delivery timing. Keep them local and specific; F1 adds
no global event bus. Preserve odysseus:notes-context-selected and its synchronous
accepted acknowledgement in future Notes work. The server remains authoritative
for owner filtering and untrusted selected IDs. Frontend APIs cannot grant access.
The checker parses executable ESM, forbids computed imports in new source,
requires exact precache entries, rejects unresolved/remapped relative imports,
and permits only canonical static JS URLs with one unversioned identity for
maintained modules. Domain code cannot mention known browser/HTTP/timer globals
or an ambient clock; use now/clock inputs. This is a conservative vocabulary
check, including shadowed names, not a semantic purity proof. Randomness, hidden
side effects, authorization and lifecycle behavior require behavioral tests.
Legacy imports from new source default to forbidden. If an incremental bridge is
necessary, declare legacyImports on that source's manifest entry as a list of
{ "specifier": "/static/js/example.js?v=existing", "reason": "contract, owner and removal slice" }.
Only feature composition, infrastructure or UI boundaries may use it. Exact
specifier/reason and target existence are required; unused exceptions fail.
Keep the existing query identity. New domain/application modules receive no
legacy exception. F1 has zero exceptions. Do not use exceptions to invent new
shared platform dependencies on feature internals.
State and lifecycle
Each feature has one authoritative mutable application-state owner. A transition operation owns writes; UI receives a view/snapshot or subscription. Preserve existing objects and initialization timing when extracting a stateful module. Do not instantiate a new store in both the legacy facade and the feature API. Compatibility facades only forward the same live implementation.
The Notes reference uses internal read-only live views to preserve captured note identity across existing asynchronous completions. Its state owner supplies all mutations; the public index exposes operations without the store. Notes deliberately retains last-completion fetch ordering. Do not infer request-generation guards from its decomposition. Its page-owned reminders survive panel closure, and panel/editor listeners and observers have separate disposal scopes.
Document lifecycle scopes: page, authenticated user, session, mounted panel and editor/draft. Name owners of subscriptions, DOM listeners, timers, observers, pending requests and caches. Mounting must be idempotent; disposal releases the resources it owns. Background reminders may outlive an open panel. Closing the panel must not silently stop those reminders during extraction.
Keep request-generation and selected-session checks around asynchronous completions. Serialize writes where the current contract requires it. Dispose subscriptions explicitly and prevent late updates to a replaced view. Feature state changes on user/session switch must follow current cleanup policy. Browser storage is for preferences/drafts/cache state, never a substitute for backend authorization, credential storage or owner filtering. Define every storage key's owner, retention/reset behavior and compatibility format before moving it.
Creating a module or introducing a feature
- Identify responsibility, owner, public consumers, side effects and behavioral evidence before editing. Start with a pure leaf when introducing a feature.
- Place only the required source file(s), for example
features/example/domain/calculate.js. Add an index only when there is a real supported feature API to compose. - Register each source in
frontend/modules.json. A typical new mapping isfeatures/example/domain/calculate.jstojs/features/example/domain/calculate.js. Preserve the source/deployment relative graph. The proof's legacy URL mapping is an explicit special case. - For an extracted leaf, the legacy feature coordinator may import its output as a temporary composition bridge. New maintained features use public indexes; the legacy tree is not globally forced into the new boundary rules in F1.
- Add the exact output URL to the appropriate explicit SW array, retain lazy
evaluation timing and bump the cache name when the graph changes. Declare
text eol=lffor both mapped paths in repository-owned.gitattributesfiles; source-tree or scoped output patterns are allowed. The publisher checks effective Git rules, including nested overrides, and rejects a missing LF rule even if output bytes already match. Do not broaden unrelated legacy checkout rules or automatically rewrite all existing query identities. - Run the publisher, inspect the source/output diff, then run architecture, identity, graph, offline and focused behavior checks. Include both sides of the mapping in review. Retiring output requires a separate reviewed deletion; the publisher does not clean the static tree.
python scripts/build_frontend.py
python scripts/build_frontend.py --check
npm ci --ignore-scripts --no-audit --no-fund
node scripts/check_frontend.mjs
node scripts/check_frontend.mjs --inventory
node --test tests/frontendArchitecture.test.mjs
./venv/bin/python -m pytest -q -rs tests/test_frontend_build.py tests/test_frontend_foundation_offline_js.py tests/test_frontend_module_graph.py tests/test_frontend_module_version_parity.py tests/test_panel_loader_js.py
./venv/bin/python scripts/verify_frontend_runtime.py
Use your actual repository virtualenv path; the F1 environment is recorded in
validation. Node 20+ follows current CI. Existing Playwright/browser prerequisites
are documented in tests/README.md and tests/KNOWN_FAILURES.md. Missing Node can
skip Python browser wrappers, which is a coverage gap, not a pass. The standalone
Node gates require locked development dependencies and fail when missing.
The publisher validates all inputs before writing and only overwrites owned
outputs whose bytes differ. It is idempotent and working-directory independent.
Git must be available for publication and --check; neither runs at application
startup. Attribute evaluation uses a private temporary Git repository and ignores
global/local overrides, so checkout consistency is reproducible in CI. It adds
no runtime configuration or backend imports. CI checks LF attributes, parity and source
syntax in existing jobs; the Node job also runs architecture self-tests. Existing
runtime identity tests remain authoritative across legacy imports, HTML and SW.
The isolated runtime probe is included in the new pytest publication tests.
Review, release and rollback
Review the source/output mapping and exact exported API, then the owner/dependency contract and behavior tests. Check manifests, URL identities, MIME/CSP, load timing, storage compatibility and disposal. Reject unexplained feature behavior changes. Do not repair unrelated defects under an extraction title.
Release from a validated revision with committed output. Native startup and packagers consume static files without a compiler. Run parity before producing a Docker or desktop artifact. Test the actual packaged application on its platform; Linux contract probes cannot certify macOS/Windows executables. Deploy matching shell, facades, graph and manifests as a unit. Keep an authorized path to the last complete artifact. Rollback restores the whole matching set, not just an entry file or cache version. Do not delete a facade until its public consumers have migrated and compatibility tests make that decision reviewable.
Proposal and PR templates
For a decomposition proposal, record: baseline; responsibility map; current public API and consumers; state owner; I/O and lifecycle boundaries; planned slice; allowed dependencies/temporary exceptions; preserved behavior; deterministic characterization tests; deployment/offline changes; overlapping PRs; rollback; explicit non-goals and remaining uncertainties.
For each migration PR, lead with the responsibility moved and resulting boundary. Record source/output paths, API compatibility, state/lifecycle ownership, characterization results, exact validation commands, platform gaps, exception removal plan, upstream dependencies and rollback. Attach a concrete before/after example when needed to establish behavioral equivalence. Preserve the repository PR template and never claim unexecuted checks.
F1 is an entirely separate foundation-only PR. Deliver the complete Notes
decomposition, regression tests, compatibility validation and final reference
case study through one draft PR targeting official dev, using one Notes branch
and worktree after F1 integration. N0 through N6 and the N5 substages are internal
engineering checkpoints, not independent PRs or merges. Each checkpoint records
focused tests, review evidence, its validated revision or reproducible snapshot,
and rollback to the prior matching source/output/facade/precache set. Keep the
draft open while those checkpoints progress; mark it ready only after the entire
decomposition passes the roadmap's acceptance gates.
Structure the Notes PR description around the final integrated design and link each checkpoint's evidence. The receiving maintainer may use smaller independent PRs for future frontend features at their discretion; the one-PR Notes decision does not impose that delivery size on later work.
Program backlog and final handoff
Priority order: review/integrate F1; resolve root offline certification with the owner of PR #6227; complete the Notes checkpoints and final reference case study within one draft PR following the roadmap; integrate the complete Notes PR after its acceptance gates; finalize the recipient handoff. Only then does the receiving maintainer prioritize Calendar, Documents, Email, Chat, Skills and Settings against current behavior and risk. Existing partial packages and high fan-in helpers need assessment, not automatic replacement.
Final H acceptance requires a designated maintainer, the integrated Notes PR revision and its internal checkpoint receipts, the actual public API/state/lifecycle map and final reference case study included in that PR, validated deployment/offline commands, known exceptions/debt, outstanding PR dependencies, rollback evidence and a reproducible independent setup by the recipient. F1 provides the handbook structure and contracts; it does not claim Notes or the completed handoff have occurred. The initiating architect then returns to separately authorized non-frontend architecture work.