mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Document chat signal projection policy
This commit is contained in:
parent
1aef614890
commit
5feae7757e
4 changed files with 20 additions and 3 deletions
|
|
@ -44,6 +44,8 @@ Obsidian and app-server boundaries stay outside Preact components. `ItemView` cl
|
|||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ Keep new code near the state or API it owns. A feature may import another featur
|
|||
|
||||
Codex Panel's runtime UI is Preact-owned. Use imperative DOM writes only for explicit bridge modules, Obsidian-owned API boundaries, or rendering and measurement code that cannot be expressed cleanly as Preact components. Chat panel-visible state belongs in `ChatStateStore` and should flow through named reducer actions and the shell-state adapter. Revisit `docs/design.md` before adding a parallel UI state or runtime path.
|
||||
|
||||
Use Preact Signals only in `src/features/chat/panel/shell-state.tsx`; lint enforces this boundary. When a surface needs fewer dependencies, add or reuse a named shell-state projection instead of importing `@preact/signals` elsewhere. See `docs/design.md` for the signal projection policy.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
Name modules by responsibility: use `Controller` only for stateful lifecycle/control surfaces, `Handler` for inbound event or request entrypoints, `Actions` for caller-facing command or callback bundles without lifecycle ownership, `Coordinator` for stateful background or cross-surface coordination, `Service` for reusable domain capabilities, `Presenter` for UI state projection, `Renderer` for render-only UI contracts, and `Host`/`Ports` for dependency boundary objects. Use `State`, `Snapshot`, `Projection`, `ViewModel`, `Options`, `Context`, `Result`, `Target`, `Capabilities`, or `ActionTargets` for data/value objects; do not use `Actions` for passive data. Use boundary/infrastructure nouns such as `Client`, `Transport`, `Cache`, `Store`, `Catalog`, `Manager`, `Bridge`, `Tracker`, `Session`, `Runtime`, `Provider`, or `Adapter` only when the object owns that concrete boundary or lifecycle role.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const removedChatStateEscapeHatchRestrictions = [
|
|||
const chatSignalAdapterRestrictions = [
|
||||
{
|
||||
selector: "ImportDeclaration[source.value='@preact/signals']",
|
||||
message: "Keep chat signals in panel/shell-state.tsx as a reducer-to-Preact notification and derived projection adapter.",
|
||||
message: "Use @preact/signals only in src/features/chat/panel/shell-state.tsx as the reducer-to-Preact derived projection adapter.",
|
||||
},
|
||||
];
|
||||
const chatDomainLayerRestrictions = [
|
||||
|
|
@ -295,7 +295,7 @@ export default defineConfig([
|
|||
files: ["tests/**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
...testTypeScriptRelaxations,
|
||||
...restrictedSyntaxRule(generatedAppServerThreadImportRestrictions),
|
||||
...restrictedSyntaxRule([...generatedAppServerThreadImportRestrictions, ...chatSignalAdapterRestrictions]),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ export function mutateState(store: ChatStateStore): void {
|
|||
expect(messages).not.toContain("codex-panel/no-chat-state-direct-mutation");
|
||||
});
|
||||
|
||||
it("allows signals in the chat shell-state adapter", async () => {
|
||||
it("allows signals only in the chat shell-state adapter", async () => {
|
||||
const messages = await lintSource(
|
||||
"src/features/chat/panel/shell-state.tsx",
|
||||
`
|
||||
|
|
@ -437,6 +437,19 @@ export const status = signal("idle");
|
|||
`
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
export const status = signal("idle");
|
||||
`,
|
||||
);
|
||||
|
||||
expect(messages).toContain("no-restricted-syntax");
|
||||
});
|
||||
|
||||
it("reports direct signals usage in tests", async () => {
|
||||
const messages = await lintSource(
|
||||
"tests/features/chat/panel/shell.test.tsx",
|
||||
`
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
export const status = signal("idle");
|
||||
`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue