Files
odysseus/docs/frontend-architecture/handbook.md
T

224 lines
14 KiB
Markdown

# Odysseus frontend architecture handbook
Status: F1 foundation and continuation guide, pending review. The final Notes
reference case study belongs to the comprehensive Notes draft PR; the recipient
handoff follows its integration. This document must evolve with reviewed internal
checkpoints and distinguish executed work from proposals.
Read the [ADR](ADR-001-source-and-deployment.md), [assessment](assessment.md),
[validation report](validation.md) and [Notes roadmap](notes-roadmap.md).
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.
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
1. Identify responsibility, owner, public consumers, side effects and behavioral
evidence before editing. Start with a pure leaf when introducing a feature.
2. 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.
3. Register each source in `frontend/modules.json`. A typical new mapping is
`features/example/domain/calculate.js` to
`js/features/example/domain/calculate.js`. Preserve the source/deployment
relative graph. The proof's legacy URL mapping is an explicit special case.
4. 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.
5. 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=lf` for both mapped paths in repository-owned `.gitattributes`
files; 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.
6. 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.
```sh
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](notes-roadmap.md).
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](notes-roadmap.md); 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.