@@ -37,7 +37,7 @@ Firefox build (the vault tab is Chrome-only for the moment). Verify in
| `service-worker` | `src/service-worker/index.ts` | extension SW / bg | yes — initialized lazily on first message |
| `popup` | `src/popup/popup.ts` | popup.html | no — goes through SW |
| `vault` | `src/vault/vault.ts` (Chrome only) | vault.html (tab) | no — goes through SW |
| `setup` | `src/setup/setup.ts` | setup.html (tab) | yes — direct dynamic import (p red ates SW handle ) |
| `setup` | `src/setup/setup.ts` | setup.html (tab) | no — goes through SW (`c reate_vault` /`attach_vault` ) |
| `content` | `src/content/detector.ts` | host page (top frame only by router check) | no |
### What each bundle owns
@@ -183,17 +183,51 @@ before any new render.
### `src/vault/`
- `vault.ts` — fullscreen tab entry. Hash-based router (`#detail/<id>` ,
`#add/<type>` , `#trash` , `#devices` , `#settings` , `#settings-vault` ,
`#history` , `#history/<id>` , `#backup` , `#import` ). Legacy
`#field-history/<id>` URLs are normalized to `#history/<id>` o n
`parseHash` (`vault.ts:139-173` ); the internal view value stays
`'field-history'` so the per-item p ane renders unchanged. Sidebar
bottom-nav: `+ new item · ▦ trash · ⌬ devices · ⚙ settings · ◷ history
· ⏻ lock` . Registers itself as the StateHost so all
`popup/components/*` renderers run unchanged. Maintains its own
`selectedItem` cache so hash navigation between already-loaded items
doesn't refetch.
- `vault.ts` (194 lines) — fullscreen tab entry, now a thin
routing + state shell after the Phase 4 split. Registers itself as
the StateHost so all `popup/components/*` renderers run unchanged,
maintains its own `selectedItem` cache so hash navigation betwee n
already-loaded items doesn't refetch, and delegates DOM scaffolding,
navigation, list/drawer/form rendering, and route dispatch to the
sibling modules below. The hash-route set is
`#detail/<id>` , `#add/<type>` , `#trash` , `#devices` , `#settings` ,
`#settings-vault` , `#history` , `#history/<id>` , `#backup` , `#import` .
- `vault-context.ts` — the `VaultController` contract plus the shared
types and pure helpers the split modules depend on. Added so the
split is acyclic: the rendering modules import the controller
interface from here rather than from `vault.ts` .
- `vault-router.ts` — hash routing + pane dispatch + data loading,
extracted to keep `vault.ts` ≤250 LOC. Owns `parseHash` ; legacy
`#field-history/<id>` URLs are normalized to `#history/<id>` here, but
the internal view value stays `'field-history'` so the per-item pane
renders unchanged.
- `vault-shell.ts` — DOM scaffolding, color-scheme apply, and the
`onMessage` wiring for the tab.
- `vault-sidebar.ts` — sidebar categories nav, 80ms-debounced search
(`SEARCH_DEBOUNCE_MS` ), and the bottom-nav
(`+ new item · ▦ trash · ⌬ devices · ⚙ settings · ◷ history · ⏻ lock` ).
Also owns the footer: a `#vault-status-slot` plus a manual `↻` refresh
button (`GLYPH_REFRESH` ). `wireSidebar` calls `refreshStatus()` once on
mount and again on the button's click — sending `get_vault_status` via
`ctx.sendMessage` and rendering the result into the slot through
`vault-status.ts` . There is **no timer polling ** : the indicator only
refreshes on mount + explicit button press, matching the spec's
no-network-without-user-intent discipline (sync is user-initiated).
- `vault-status.ts` — sidebar-footer sync indicator renderer.
`renderStatusIndicator(el, status)` is pure DOM: it renders, by
priority, `N pending` / `N ahead` / `N behind` , falling back to
`in sync` , plus a `last sync <relativeTime>` / `never synced` line.
Reuses `shared/glyphs.ts` (`GLYPH_PENDING` /`AHEAD` /`BEHIND` /`SYNCED` )
and `shared/relative-time.ts` . `VaultStatus` is an alias of
`GetVaultStatusResponse['data']` , so the renderer's input shape is
single-sourced from the message contract and can't drift from the SW
handler.
- `vault-list.ts` — the list pane and its row rendering.
- `vault-drawer.ts` — drawer open/close/render plus
`ensureDrawerClosedForRoute` , which closes the drawer on any
non-list navigation.
- `vault-form-wrapper.ts` — `renderFormWrapped` plus the sticky bar and
header that wrap form panes.
- `vault.html` / `vault.css` — sidebar + pane layout.
### `src/vault/components/`
@@ -211,12 +245,19 @@ exports `render…(app)` and a `teardown()`, same convention as
### `src/setup/`
- `setup.ts` (1137 lines) — the wizard state machine. Six steps
(0..5): mode picker (new vault / attach this device), host type
(Gitea/GitHub), host config + connection test + repo probe, the
forking step 3 (create-vault vs attach-this-device), device name,
finish. Loads WASM directly. State-coupled `updateStrengthUi` stays
here because it walks the live wizard state .
- `setup.ts` (58 lines) — a thin UI-only shell after the Phase 3
split: the render loop + progress track + boot + re-exports. No longer
imports `relicario-wasm` ; the wizard now drives vault creation/attach
through the SW. Binds `clearWizardState` to
`window.addEventListener('beforeunload', clearWizardState)`
(`setup.ts:53` ) and also calls it on `goto('mode')` (`setup.ts:44` ) .
- `setup-steps.ts` (extracted in Phase 3) — the setup step registry +
wizard state + `clearWizardState` + `finishSetup` . One-directional
import (`setup.ts` → `setup-steps.ts` , no cycle). Crypto orchestration
no longer lives in the wizard: the device step (where `deviceName`
exists) fires `create_vault` and `attach_vault` SW messages instead of
calling WASM directly. State-coupled `updateStrengthUi` stays here
because it walks the live wizard state.
- `setup-helpers.ts` (84 lines, extracted in commit `f79a67b` ) — pure
helpers: `escapeHtml` , `ratePassphrase` , `scheduleRate` (150ms
debounced zxcvbn round-trip), `STRENGTH_LABELS` , `entropyText` , the
@@ -273,7 +314,23 @@ exports `render…(app)` and a `teardown()`, same convention as
`session.getCurrent()` , load via `vault.fetchAndDecrypt*` , mutate,
re-encrypt, and `gitHost.writeFile` . `fill_credentials` lives here
with its own captured-tab verification (see Key flows). New in
commit `a7dbf35` : `register_this_device` .
commit `a7dbf35` : `register_this_device` . Phase 3 added
`create_vault` and `attach_vault` (full SW-side vault
creation/attach: embed/unlock, encrypt+push, `register_device` +
`addDevice` , persist config+image, `session.setCurrent` ; the failure
path locks and frees the handle). The `lock` handler now also nulls
`state.gitHost` (symmetric with session-expiry) so the status cache
can't go stale across a lock→unlock. Phase 6 added `get_vault_status`
(popup-only, read-only) — returns the cached sync summary
`{ ahead, behind, lastSyncAt, pendingItems }` with **no network
call**. `ahead` /`behind` /`lastSyncAt` are read straight off
`state.gitHost` (populated by the `sync` handler, which records
`lastSyncAt = Math.floor(Date.now()/1000)` — unix **seconds ** — after
a successful manifest fetch). `pendingItems` is a live count of active
(non-trashed) manifest entries via `vault.listItems(manifest).length` .
`ahead` /`behind` are structurally always `0` in the extension (it
writes straight to the host via the Contents REST API; there is no
local commit graph) and exist for parity with `relicario status` .
- `router/content-callable.ts` — handler match arms for every
`CONTENT_CALLABLE_TYPES` message. Origin always derived from
`sender.tab.url` , never from message fields. `capture_save_login`
@@ -287,7 +344,13 @@ exports `render…(app)` and a `teardown()`, same convention as
no www-stripping, no public-suffix), trash helpers
(`listTrashed` , `restoreItem` , `purgeItem` , `purgeAllTrash` ), and
attachment helpers (`addAttachmentToItem` , `removeAttachmentsFromItem` ,
with manifest summary sync).
with manifest summary sync). Now also includes the
`create_vault` /`attach_vault` orchestration handlers (Phase 3) and
`handleGetVaultStatus(state)` (Phase 6) — synchronous, no network;
returns the cached `{ ahead, behind, lastSyncAt, pendingItems }` . Its
`Pick<GitHost,'lastSyncAt'|'ahead'|'behind'>` -typed param both breaks
the `PopupState` import cycle and structurally forbids it from making
a network call.
- `session.ts` — single module-scope `SessionHandle | null` . α assumes
one vault per install. Multi-vault would replace this with a `Map`
keyed by vault id.
@@ -301,6 +364,15 @@ exports `render…(app)` and a `teardown()`, same convention as
`putBlob` , `getBlob` , `deleteBlob` ) and the `createGitHost` factory.
`BLOB_THRESHOLD_BYTES = 900*1024` is the cutover point at which
attachment writes switch from the Contents API to the Git Data API.
The `GitHost` interface also carries cached sync metadata —
`lastSyncAt: number | null` (unix seconds), `ahead: number` ,
`behind: number` — initialized to `null` /`0` /`0` in both `GiteaHost`
and `GitHubHost` . The cache rides the gitHost lifecycle: created on
unlock and cleared whenever `state.gitHost` is nulled — on
session-timer expiry (`index.ts` ) **and ** on the explicit `lock`
message handler (`popup-only.ts` ), which now nulls `state.gitHost`
symmetrically so a lock→unlock cycle can't surface a stale
`lastSyncAt` .
- `gitea.ts` / `github.ts` — the two GitHost implementations. Both use
the host's Contents API for files under threshold, and Git Data API
(blobs + tree + commit) for large attachment uploads. Auth differs
@@ -322,7 +394,9 @@ exports `render…(app)` and a `teardown()`, same convention as
- `state.ts` — `StateHost` interface + module-scope singleton. Both
`popup.ts` and `vault.ts` register themselves on boot. All
`popup/components/*` import from here, never from popup.ts directly,
so the same render code runs in both bundles.
so the same render code runs in both bundles. Its `sendMessage`
wrapper intercepts `vault_locked` responses (lifted out of `vault.ts`
in Phase 4, so the intercept now applies uniformly to both bundles).
- `types.ts` — TypeScript mirrors of the Rust core's serde shapes:
`Item` , `ItemCore` (internally-tagged on `type` ), `Field` and
`FieldValue` (adjacently-tagged on `kind` / `value` ), `Manifest` ,