diff --git a/docs/adr/0060-network-aware-ui-gating-for-remote-features.md b/docs/adr/0060-network-aware-ui-gating-for-remote-features.md new file mode 100644 index 00000000..bcc92922 --- /dev/null +++ b/docs/adr/0060-network-aware-ui-gating-for-remote-features.md @@ -0,0 +1,28 @@ +--- +type: ADR +id: "0060" +title: "Network-aware UI gating for remote-dependent features" +status: active +date: 2026-04-13 +--- + +## Context + +Some app features require an active internet connection (e.g., cloning the Getting Started vault template from GitHub). Prior to this decision, the UI would attempt the operation and surface a generic error only after failure. Users on first launch in offline environments got a confusing error when trying to use the template. + +## Decision + +**Introduce a `useNetworkStatus` hook that tracks `navigator.onLine` via `online`/`offline` DOM events, and use it to proactively gate UI surfaces that require a network.** Features that depend on remote access (clone, sync) show an explanatory message and disable their action button when the device is offline, rather than failing silently at execution time. + +## Options considered + +- **Option A** (chosen): `useNetworkStatus` hook + proactive UI disable — disables the action before the user tries it, with inline copy explaining the offline state. +- **Option B**: Attempt and catch — let the operation run and surface the error in a toast. Simpler, but poor UX for first-launch users who don't know what went wrong. +- **Option C**: Check connectivity with a ping on demand — more accurate but adds latency and complexity; `navigator.onLine` is sufficient for the use case. + +## Consequences + +- Positive: cleaner first-run experience for offline users; no misleading error messages. +- Positive: `useNetworkStatus` is a reusable hook for future remote-gated features. +- Negative: `navigator.onLine` can return `true` on a captive-portal / no-internet network — the hook reflects OS-level connectivity, not end-to-end reachability. The operation may still fail with a network error, which must still be handled. +- Re-evaluate if the app adds more remote features that need finer-grained reachability checks. diff --git a/docs/adr/0061-ai-prompt-bridge-event-bus.md b/docs/adr/0061-ai-prompt-bridge-event-bus.md new file mode 100644 index 00000000..1f22d3d8 --- /dev/null +++ b/docs/adr/0061-ai-prompt-bridge-event-bus.md @@ -0,0 +1,29 @@ +--- +type: ADR +id: "0061" +title: "AI prompt bridge — module-level event bus for cross-component prompt routing" +status: active +date: 2026-04-13 +--- + +## Context + +The AI panel is a sibling subtree to the command palette in the component tree. When the user submits a prompt from the command palette's AI mode, the AI panel (mounted elsewhere) needs to receive it and start processing. Props-down / callbacks-up wiring between the two would require threading state through multiple layers of unrelated components. + +## Decision + +**Introduce `aiPromptBridge.ts` as a module-level singleton event bus.** The bridge exposes `queueAiPrompt(text, references)` (write path) and `takeQueuedAiPrompt()` (consume path), backed by a module variable and a `CustomEvent` on `window` (`tolaria:ai-prompt-queued`). The command palette enqueues a prompt; the AI panel listens for the event, consumes the prompt via `takeQueuedAiPrompt`, and dispatches it to the agent. A companion `requestOpenAiChat()` function fires a separate `tolaria:open-ai-chat` event to open the panel before the prompt is sent. + +## Options considered + +- **Option A** (chosen): module-level singleton + `window` events — zero dependencies, no new global state manager, consistent with the existing `window.dispatchEvent` pattern already used for menu-command bridging. +- **Option B**: Lift AI panel state to a shared ancestor (e.g., `App.tsx`) and pass `onPrompt` callback down — would require `App.tsx` to own AI agent state, bloating it further; conflicts with ADR-0026 (props-down principle). +- **Option C**: Zustand / Jotai global store atom — adds a dependency and architecture overhead for a narrow, two-participant channel. + +## Consequences + +- Positive: decouples command palette from AI panel with no shared ancestor coupling. +- Positive: any future surface (e.g., wikilink context menu, note action bar) can call `queueAiPrompt` without tree-level wiring. +- Negative: module-level mutable state is harder to test in isolation; tests must call `takeQueuedAiPrompt` to drain state between runs. +- Negative: the event is fire-and-forget — if the AI panel is not mounted when the event fires, the prompt is silently dropped (currently not an issue as the panel is always mounted). +- Re-evaluate if the number of AI entry points grows large enough to warrant a proper state management solution. diff --git a/docs/adr/README.md b/docs/adr/README.md index 5bdc994a..79805c49 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -115,3 +115,5 @@ proposed → active → superseded | [0057](0057-alpha-stable-release-channels-and-beta-cohorts.md) | Alpha/stable release channels with PostHog beta cohorts | active | | [0058](0058-claude-code-first-launch-onboarding-gate.md) | Claude Code first-launch onboarding gate | active | | [0059](0059-local-only-git-commits-without-remote.md) | Local-only git commits for vaults without a remote | active | +| [0060](0060-network-aware-ui-gating-for-remote-features.md) | Network-aware UI gating for remote-dependent features | active | +| [0061](0061-ai-prompt-bridge-event-bus.md) | AI prompt bridge — module-level event bus for cross-component prompt routing | active |