experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(architecture): add Implementation conventions section

Document the recurring corner-case rules that don't belong in the
state-machine or lifecycle docs but still bite often enough to need a
canonical reference: bgWindow as orchestrator, declarative-publish-type
command requirements, HUD widget conventions, peek-card slot handling,
window-open IPC gotchas (centered-coords ordering, isDestroyed guards,
workspaceKey collision), page-host overlay anchoring, groups UI column
typing, canvas page rewriting (-webkit-mask-image / box-shadow conflict),
visible-content-area-sacred rule, Electron ESC handling, editor pane
font-family requirement, pubsub event publishing (tagItemAndPublish vs
direct tagItem), the noun system entry points, sync architecture
(device IDs, _sync metadata, push filter), and the "extension"-as-smell
naming rule.

Most of this content was previously living as scattered runtime gotchas;
collecting it under one Implementation conventions heading makes it
discoverable for any contributor reading docs/architecture.md cold.

+157
+157
docs/architecture.md
··· 397 397 398 398 --- 399 399 400 + ## Implementation conventions 401 + 402 + A grab-bag of conventions that aren't structural but recur often enough 403 + to bite. The state machines and lifecycle docs cover the spine; this 404 + section covers the corners. 405 + 406 + ### bgWindow is the orchestrator 407 + 408 + The background window (`peek://app/background.html`) is THE core 409 + orchestrator. The cmd, hud, and page surfaces are consolidated into it. 410 + Every published event must reach it; if you find a producer publishing 411 + to a renderer that bgWindow doesn't observe, the routing is wrong. 412 + 413 + ### Declarative publish-type commands 414 + 415 + Commands of `type: 'publish'` must publish a `:result` event back so the 416 + cmd panel proxy resolves. Otherwise the panel hangs for 30 s before 417 + timing out. 418 + 419 + ### HUD widgets 420 + 421 + HUD widgets live in the core renderer (not a separate tile) and are 422 + loaded via `initHud()` inside `app/index.js`. They are eager, 423 + `alwaysOnTop: true`, and `focusable: false`. 424 + 425 + - App focus drives hide/show via main-process `did-resign-active` / 426 + `did-become-active`. 427 + - The `__hudHidden` flag plus `trackVisibleWindowFocus()` skips 428 + non-focusable windows so they don't override focused-window targeting. 429 + 430 + ### Cmd panel state machine 431 + 432 + `app/cmd/state-machine.js` is a pure module with no DOM/IPC deps. Ten 433 + states: IDLE, TYPING, RESULTS_OPEN, PARAM_MODE, EXECUTING, 434 + OUTPUT_SELECTION, CHAIN_MODE, CHAIN_POPUP, ERROR, CLOSING. Actions run 435 + BEFORE `currentState` is updated. See 436 + [docs/cmd-state-machine.md](./cmd-state-machine.md) for the full spec 437 + and test coverage notes. 438 + 439 + ### peek-card component 440 + 441 + - Empty slot sections are hidden via `slotchange` + the `slot-empty` 442 + class. CSS `:empty` does not work on slot elements. 443 + - **Card action buttons must always be visible.** Never hover-reveal 444 + them. 445 + 446 + ### Window open IPC 447 + 448 + - External URLs need centered coordinates computed BEFORE canvas bounds 449 + adjustment (otherwise undefined `x`/`y` produce `NaN`). 450 + - `handleExternalUrl` is exposed on the `__peek_test` global for 451 + Playwright. 452 + - `window._cmdState` in the cmd panel exists for deterministic test 453 + waits. 454 + - Main-process `setTimeout` bodies that reference a window or 455 + `webContents` must guard `isDestroyed()` — closing inside the 456 + deferred callback throws unhandled. 457 + - `workspaceKey`'d tiles collapse opens — Playwright cannot get a fresh 458 + window when the key matches an existing one. Static manifest 459 + assertions are the fallback. 460 + 461 + ### Page widget panels 462 + 463 + Page info (top-left) and entities (right-side) live in 464 + `app/page/page.js`. The entities panel subscribes to `entities:extracted` 465 + pubsub and falls back to DOM extraction after 2 s. 466 + 467 + Page-host overlays (load error, etc.) belong inside `.center-column` 468 + with `position: absolute; inset: 0`, NOT on `<body>` with 469 + `position: fixed` — fixed-positioned overlays stretch with `show()`'s 470 + window expansion. 471 + 472 + ### Groups UI 473 + 474 + `promoteTagToGroup()` must only pass valid DB columns to `setRow()`. 475 + Runtime-only props like `addressCount` cause silent SQL failures. 476 + 477 + ### Canvas page rewriting 478 + 479 + Web URLs are rewritten on open: `https://example.com` → 480 + `peek://app/page/index.html?url=...`. Group mode context is set on new 481 + windows BEFORE `loadURL`; `openWindow` falls back to 482 + `lastFocusedVisibleWindowId` when no opener is provided. 483 + 484 + CSS gotcha: `-webkit-mask-image` on a webview clips `box-shadow` — 485 + use `border` + `outline` instead. 486 + 487 + ### Webview / visible content area 488 + 489 + The visible content area must NEVER resize except via direct user 490 + interaction. Window + chrome may grow (e.g. for navbar reveal), but the 491 + visible content stays put. 492 + 493 + ### Electron ESC handling 494 + 495 + Electron's `before-input-event` intercepts ESC at the main-process level 496 + and calls `e.preventDefault()`. DOM `keydown` listeners for ESC never 497 + fire in the renderer — textarea / input keydown listeners are dead code 498 + for ESC. 499 + 500 + ESC is delivered via `escape-pressed` IPC → preload `_escapeCallback` → 501 + the tile's `onEscape` handler. To handle ESC in custom UI elements, 502 + check `document.activeElement` in the `onEscape` handler, blur/close as 503 + needed, return `{ handled: true }`. 504 + 505 + `escUnhandledPolicy()` is the second-phase handler; it opens the 506 + Windows switcher at the workspace root. 507 + 508 + ### Editor pane architecture 509 + 510 + Four panes: outline, editor (center), preview, notes/annotations. 511 + Collapse icons MUST set `font-family: system-ui` on toggle elements; 512 + ServerMono lacks the Unicode glyphs. 513 + 514 + ### PubSub event publishing 515 + 516 + - Direct `tagItem()` calls do NOT publish events — UIs won't update 517 + reactively. Use `tagItemAndPublish()` in `ipc.ts` for any tagging that 518 + should trigger a UI refresh. 519 + - Same pattern applies to any data mutation that skips the IPC handler: 520 + it must manually publish. 521 + - Groups UI listens for: `tag:item-added`, `tag:item-removed`, 522 + `tag:created`, `item:created`, `item:deleted`, `sync:pull-completed`. 523 + - The Tags page widget (`app/page/page.js`) also subscribes to 524 + `tag:item-added` / `tag:item-removed` and resolves `currentItemId` on 525 + the first tag event when null. 526 + 527 + ### Noun system 528 + 529 + `features/cmd/noun-registry.js` + `features/cmd/nouns.js`. Register a 530 + noun via `registerNoun({ name, singular, query, create, browse, ... })` 531 + and the system auto-generates: bare command, `list {name}`, 532 + `new {singular}`, `open {singular}`. Used by groups, tags (with 533 + `skipBare`), and lex (singular `lex`, NSID-filtered query). Prefer 534 + nouns over registering many individual commands — avoids command 535 + registry pollution. 536 + 537 + ### Sync architecture 538 + 539 + - Device IDs are raw UUIDs (no `desktop-` / `mobile-` / `extension-` 540 + prefixes). 541 + - `_sync` metadata lives inside the item `metadata` JSON: 542 + `{ _sync: { createdBy, createdAt, modifiedBy, modifiedAt } }`. 543 + - `syncSource` was removed from desktop Electron, server, and 544 + `sync/sync.js`; it still exists in iOS `lib.rs` and Tauri `sync.rs` 545 + pending refactor. 546 + - Push filter: `syncedAt === 0` OR `updatedAt > syncedAt`. 547 + - E2E sync tests use `E2E_TEST=true` to bypass auth. 548 + 549 + ### "Extension" is a smell when used for tiles 550 + 551 + Real Chrome MV3 extensions live in `chrome-extensions/` and keep the 552 + "extension" name. Anywhere else, "extension" used as a synonym for 553 + tile / feature is a leftover from v1 and should be renamed. 554 + 555 + --- 556 + 400 557 ## See also 401 558 402 559 - [docs/tiles-single-file.md](./tiles-single-file.md) — the "single-file