murashit_codex-panel/docs/design.md
2026-06-22 19:04:55 +09:00

7.9 KiB

Design

Codex Panel is an Obsidian surface for Codex. It exists to put Codex beside vault notes without becoming a separate AI client, runtime policy editor, terminal, search product, or writing suite.

This document records durable design direction. User-facing behavior belongs in README.md; daily workflow, source layout, generated files, and compatibility checks belong in docs/development.md.

Product Boundary

Keep the panel thin. Codex Panel owns the Obsidian experience around Codex: panels, thread display, composing, approvals and user input, vault-aware link handoff, archive export, settings for panel preferences, diagnostics, and selection rewrite review.

Codex owns runtime policy and thread truth. Model defaults, sandboxing, approvals, MCP servers, hooks, providers, network access, thread history, archived threads, effective configuration, goals, and runtime settings should come from Codex through codex app-server.

Panel settings should store only panel-specific preferences. Do not duplicate Codex configuration into Obsidian settings just because a value is useful to display or inspect.

Sources of Truth

The repository checkout is the source of truth for implementation, version control, and release work. Ignored release assets are generated outputs, even when Obsidian loads them at runtime.

codex app-server is the source of truth for Codex state. Panel-side caches exist to keep the UI stable across transient failures; failed reads or stale panels must not become authoritative empty state.

The app-server API is experimental. The project tracks the supported Codex CLI minor and favors a clean current flow over broad old-protocol compatibility. Current optional and nullable fields are still part of the contract and must be normalized before display.

Runtime settings move through three panel-owned layers before they reach Codex. Chat state stores active runtime values reported by app-server plus pending runtime intents requested by the user; those intents are not app-server payload values. Effective runtime projections combine pending intent, active thread state, and runtime config for display and UI decisions. Only the app-server boundary converts intent into transport values: undefined omits a field, null explicitly clears or resets it, and concrete values set it. Config values are inputs to effective projection and selected start-thread defaults; they should not be treated as synonymous with transport null.

Fast mode is a panel runtime intent, not an arbitrary service tier override. The app-server still owns service tier ids and defaults, so the panel converts Fast mode enablement to the model catalog's Fast tier id when available, and converts Fast mode disablement or service-tier reset to an explicit clear request at the transport boundary.

Code Boundaries

Generated app-server protocol types should stay behind src/app-server/. Services at that boundary adapt protocol payloads into panel-owned domain models or small projections before data reaches features, workspace coordination, settings, or UI. Feature-local app-server integration modules may route and apply those projections, but they should not hand-copy generated request or response shapes when a shared app-server protocol adapter can own that mapping.

Turn stream conversion is the main exception: raw app-server stream payloads may be consumed at the conversion boundary because the event set is broad and changes with Codex. The rest of chat state and UI should still use panel-owned display models.

Source modules should be organized by reason to change, not by the single Obsidian plugin entrypoint. Obsidian lifecycle, app-server transport, chat state, runtime display, thread management, selection rewrite, settings, and workspace coordination should each stay close to the state or API they own.

Feature-to-feature imports are acceptable when the imported feature owns a real capability. Generic helpers belong in src/shared/, generated-independent meaning belongs in src/domain/, and protocol adaptation belongs in src/app-server/.

Do not hide complexity behind forwarding layers. Add an abstraction only when it owns a lifecycle, boundary, state transition, or reusable domain capability.

UI Ownership

Runtime UI composition is Preact-owned. Preact components should render the panel shell, toolbar, message stream, composer, and request controls.

Obsidian and app-server boundaries stay outside Preact components. ItemView classes, plugin lifecycle, app-server connections, controllers, MarkdownRenderer, editor APIs, notices, and workspace operations belong in boundary modules.

Chat-visible state belongs in ChatStateStore and named reducer actions. Signals and components may project that state, but they should not become parallel sources of truth for turns, pending requests, runtime settings, history cursors, or open details.

Preact Signals are a shell-local projection adapter, not a second state system. The chat panel may use signals to mirror reducer slices and derive UI-facing facts such as busy state, active turn id, message stream item groups, action targets, pending request blocks, and composer runtime snapshots. Those derived facts should be read by surface projections through small shell-state contracts instead of making components or presenters depend on broad reducer slices. Domain, application, host, presentation, and component modules should keep using pure selectors, reducer actions, and explicit props rather than importing signals directly.

Imperative DOM bridges are allowed when an external API or measurement problem requires an HTMLElement, such as Obsidian markdown rendering, rendered link binding, diff rendering, icon rendering, textarea measurement, and message stream scroll anchoring. They should not become a second UI composition system inside Preact-owned surfaces.

Interaction Principles

Multiple panels are separate Obsidian leaves. Treat each panel as its own Codex working surface with independent connection, thread, turn state, composer, and pending requests.

Thread history, archived state, forks, and catalog data should follow app-server semantics. Obsidian integrations such as archive note export are convenience views of Codex state, not replacements for Codex history.

Selection rewrite is intentionally scoped: use the live editor buffer, ask Codex for a replacement candidate, show a focused diff, and apply only after user confirmation. Avoid expanding it into a broader writing assistant without a separate design decision.

Server requests should become panel UI only when the user can naturally answer them in context. Unknown or unsupported requests should stay diagnostic instead of pretending to be normal conversation text.

Message stream display should separate primary conversation from diagnostic detail and progress/status. Preserve stable item identity across history reads, streaming updates, delayed rendering, and scroll anchoring.

Codex Panel UI should feel native inside Obsidian. Prefer Obsidian variables, standard classes, and side-panel patterns. Add custom visual treatment only when Codex-specific state would otherwise be hard to read.

Testing Direction

Tests should protect user expectations, app-server/panel responsibility boundaries, and state-transition invariants.

Prefer tests for visible behavior: independent panels, thread-scoped resets, pending request handling, approval flows, readable transcript/detail/status grouping, scroll preservation, and display fallbacks for structured app-server values.

Avoid tests that freeze incidental implementation details such as exact DOM nesting, render counts, node reuse, helper decomposition, or no-op array updates unless those details directly protect a user-visible invariant.

Panel tests may define how received structured values are displayed, retained, or normalized. They should not redefine Codex-owned runtime policy, model lists, sandbox behavior, approval policy, or thread history semantics.