refactor: restructure docs as submodule, add dev-docs/ and AGENTS.md

- Move ASPICE docs to chanoraapp/docs submodule at docs/
- Move development docs to dev-docs/ (superpowers, offline-knowledge, impl-mapping)
- Add AGENTS.md with project conventions for AI agents
- Add impl-mapping.md (SAD component → source file mapping)
- Archive completed plans to dev-docs/superpowers/plans/_archived/
- Remove AGENTS.md from .gitignore (now tracked)
This commit is contained in:
Edison Jwa
2026-06-13 03:32:33 +09:00
parent 5765e9cf6f
commit bba6273af7
98 changed files with 2113 additions and 30548 deletions
@@ -0,0 +1,116 @@
# Chanora Server Prefetch Crate Design
Date: 2026-05-28
## Goal
Move server-resolution prefetch policy out of `chanora_core` into a focused Rust crate named `chanora_prefetch`, without changing connection behavior, Flutter APIs, or protocol dialing semantics.
## Non-Goals
- Do not change resolver behavior or DNS/SRV/TSDNS ordering.
- Do not change `ConnectConfig.resolved_address` semantics in `chanora_protocol`.
- Do not expose prefetch state in the UI.
- Do not prefetch bookmarks or additional hosts.
- Do not persist prefetched addresses.
## Architecture
Add a workspace member at `crates/chanora_prefetch`.
Responsibilities:
- Normalize server host keys by trimming and lowercasing.
- Track one active prefetch generation.
- Store at most one successful prefetched socket address.
- Reject stale async completions by generation.
- Return a prefetched address only for an exact normalized host match.
- Enforce the 2-minute freshness TTL.
- Resolve server addresses by calling `chanora_resolver::ChanoraResolver::resolve_client_address`.
- Log prefetch start, success, miss/stale, and failure diagnostics.
Dependencies:
- `chanora_resolver` for actual server address resolution.
- `tokio` for `Mutex` and spawned prefetch tasks.
- `tracing` for diagnostics.
- `thiserror` for a narrow `ServerPrefetchError` public error type.
## Public API
The crate exposes this small async owner type:
```rust
pub struct ServerPrefetcher { ... }
impl ServerPrefetcher {
pub fn new() -> Self;
pub async fn prefetch(&self, host: String) -> Result<(), ServerPrefetchError>;
pub async fn fresh_match(&self, host: &str) -> Option<std::net::SocketAddr>;
}
```
`prefetch` returns after scheduling work, preserving the current invisible, non-blocking behavior. Empty normalized hosts are ignored successfully. Resolution failures are stored only as diagnostics and do not affect connect semantics.
## Core Integration
`chanora_core` replaces its private prefetch cache fields and helpers with `ServerPrefetcher`.
Core remains the trust boundary for connection config:
- It clears any caller-provided `ConnectConfig.resolved_address` before lookup.
- It asks `ServerPrefetcher::fresh_match` for the current host.
- It sets `dial_cfg.resolved_address` only from a fresh exact cache hit.
- It stores supervisor/reconnect config with `resolved_address: None`.
The Flutter bridge keeps calling the same core API, `prefetch_server_resolution(host)`. No Dart API change is intended.
## Data Flow
1. Flutter host editing schedules `ChanoraSession::prefetch_server_resolution(host)`.
2. Core delegates to `ServerPrefetcher::prefetch(host)`.
3. The prefetcher normalizes the host, increments generation, and spawns resolver work.
4. On success, the prefetcher stores the resolved socket address if the generation is still current.
5. On connect, core prepares `(stored_cfg, dial_cfg)`.
6. Core clears untrusted `resolved_address`, asks the prefetcher for a fresh exact match, and applies the result only to `dial_cfg`.
7. Protocol uses `dial_cfg.resolved_address` if present; otherwise it resolves normally.
## Error Handling
- Prefetch failures remain invisible to users.
- Prefetch failures are logged through `tracing`.
- If prefetch misses, is stale, or fails, connect falls back to normal protocol resolution.
- A caller-supplied `resolved_address` is never trusted by core.
## Testing
Move cache-policy tests from `chanora_core` into `chanora_prefetch`:
- fresh exact match returns the socket address.
- stale entries are ignored.
- different hosts are ignored.
- stale generation completions are ignored.
- blank normalized hosts return `Ok(())` and do not update generation or spawn resolver work.
Keep core tests for connection trust-boundary behavior:
- untrusted `resolved_address` is cleared on cache miss.
- prepared stored/supervisor config has `resolved_address: None`.
- prepared dial config can receive a fresh prefetched address.
Run at minimum:
- `cargo test -p chanora_prefetch --lib`
- `cargo test -p chanora_core --lib`
- `cargo test -p chanora_protocol --lib`
For final confidence, rerun the Android server connect smoke path that verifies prefetch logs and connected UI.
## Acceptance Criteria
- Workspace builds with the new crate member.
- `chanora_core` no longer owns the prefetch cache implementation.
- `chanora_prefetch` owns prefetch normalization, TTL, generation, storage, and resolver-backed warming.
- Public Flutter and Rust protocol behavior is unchanged.
- Existing server connect and reconnect safety tests pass.
- Android connect still reaches the connected server UI and does not get stuck in `Connecting` or `Synchronizing`.
@@ -0,0 +1,203 @@
# Server Resolution Prefetch Design
Date: 2026-05-28
## Purpose
Reduce perceived server join latency by resolving the active TeamSpeak server address before the user taps Connect. Prefetch must be invisible, conservative, and safe: it may warm resolver state, but it must not change connection semantics or surface background errors to the user.
Recent Android testing showed resolver latency can dominate the first part of the connect flow. A prior fix bounded slow TS3 SRV discovery and removed Android's forced Cloudflare resolver. Prefetch builds on that by hiding remaining address-resolution work when the user has already entered or loaded a likely server address.
## Goals
- Prefetch only the active server address field.
- Keep the feature invisible to users.
- Reuse prefetched results only for exact normalized host matches.
- Keep prefetched results fresh for 2 minutes.
- Preserve today's Connect behavior when prefetch misses, fails, or is stale.
- Avoid prefetching all bookmarks.
- Avoid opening a TS3 session before the user taps Connect.
## Non-Goals
- No visible resolving, ready, or failed UI state.
- No bookmark fan-out prefetch.
- No persisted resolver cache across app launches.
- No password, channel, or permission validation during prefetch.
- No server reachability probe beyond address resolution.
- No connection warm-up or pre-authentication.
## Chosen Approach
Use a Rust-owned resolver prefetch cache with Flutter-owned scheduling.
Flutter knows when the active host field changes, so it schedules prefetch requests. Rust owns resolver correctness, normalization, cache validity, and connect-time reuse. This keeps Flutter from depending on resolver internals and ensures Connect can independently decide whether a prefetched result is safe to use.
Other approaches considered:
- Flutter-only prefetch: rejected because it pushes resolver state into Dart and creates a weaker boundary between UI and connection behavior.
- Resolver-internal repeated-call cache only: rejected because it does not hide first-click latency from the active host field.
## Behavior
Prefetch starts for the active host value in two cases:
- After `_loadUiSettings()` loads the last-used host into `_hostCtl`.
- After the user stops editing the host field for about 700 ms.
The feature is invisible:
- No SnackBars.
- No inline status text.
- No disabled Connect button.
- No user-facing error if prefetch fails.
Connect behavior:
- If the current normalized host exactly matches a fresh prefetched entry, Connect uses the cached resolved address.
- If the cache is missing, stale, failed, or for a different host, Connect resolves normally.
- Connect remains the only operation that opens a TS3 session.
## Architecture
### Flutter Scheduling
`_BetaHomeState` owns the host text field. It should add a listener to `_hostCtl` and manage a short debounce timer.
Responsibilities:
- Trim the host input before scheduling.
- Skip empty values.
- Reset the debounce timer on each edit.
- Call a bridge prefetch API after about 700 ms of idle typing.
- Schedule one prefetch after settings load if the loaded host is non-empty.
- Dispose the listener and timer with the widget state.
Flutter does not store resolved addresses and does not decide whether Connect can use a prefetched result.
### Bridge API
Add a fire-and-forget bridge function shaped like:
```text
prefetch_server_resolution(host: String) -> Result<(), BridgeError>
```
The bridge call should return after the prefetch task has been accepted by the Rust runtime. It must not wait for resolution to complete. Background completion or failure is reported only through diagnostics/logging.
### Rust Resolver Cache
Rust stores a small prefetch cache owned near the session/resolver boundary. A single latest-host entry is enough for v1, because the design only prefetches the active field.
Cache entry fields:
- Normalized input host.
- Resolved `host:port` address.
- Resolution method.
- Completion timestamp.
- Generation or request id.
- Optional sanitized failure metadata for diagnostics.
The cache TTL is 2 minutes.
### Connect Integration
Connect should ask Rust for a fresh exact-match prefetched result before running normal resolution.
Rules:
- Exact normalized host match is required.
- Entry age must be at most 2 minutes.
- Failed entries must not block normal connect resolution.
- Stale entries must be ignored.
- Missing cache must behave exactly like today.
## Data Flow
1. App starts.
2. `_loadUiSettings()` loads the last-used host into `_hostCtl`.
3. Flutter schedules invisible prefetch for that host.
4. User edits the host field.
5. Flutter cancels the pending debounce timer and starts a new one.
6. After 700 ms idle, Flutter calls Rust prefetch with the latest trimmed host.
7. Rust normalizes and resolves the host through the same resolver path used by Connect.
8. Rust stores the result if it still matches the latest generation for that normalized host.
9. User taps Connect.
10. Rust Connect checks the cache for a fresh exact-match result.
11. Cache hit: Connect uses the prefetched address.
12. Cache miss/stale/failure: Connect resolves normally.
## Cancellation And Staleness
Cancellation can be logical rather than hard task cancellation.
- Flutter prevents obsolete debounce timers from firing.
- Rust tags requests by normalized host and generation.
- Late completions for stale generations must not replace newer successful entries.
- Duplicate prefetches for the same normalized host may coalesce or refresh the same entry.
This avoids complexity while preventing old input values from poisoning the cache.
## Error Handling
Prefetch failures are diagnostic-only.
- Empty host: skip prefetch.
- Invalid host shape: skip or fail silently with debug diagnostics.
- Resolver failure: store optional sanitized failure metadata for diagnostics only.
- Connect after failure: normal connect path runs and surfaces errors as it does today.
- App resume and network changes: no special invalidation in v1; TTL handles staleness.
- Disconnect: cache may remain because it is independent of the TS3 session.
## Diagnostics
Add privacy-safe logs for:
- Prefetch started.
- Prefetch result.
- Prefetch failed.
- Connect using prefetched resolution.
- Connect prefetch miss or stale entry.
Do not log passwords, channel passwords, or nickname. Host and resolved address are acceptable because resolver/connect logging already includes them today.
## Testing
Rust tests:
- Fresh exact-match prefetched result is reusable.
- Stale prefetched result is ignored.
- Different normalized host is ignored.
- Failed prefetch does not block normal resolution.
- Late stale generation cannot overwrite a newer cache entry.
Flutter tests should cover the scheduling logic through a small testable helper if wiring directly through `_BetaHomeState` would be brittle:
- Host edits debounce prefetch scheduling.
- Empty host does not prefetch.
- Settings-loaded host schedules one prefetch.
Manual Android smoke test:
- Install debug APK.
- Launch app.
- Wait for last-used host prefetch or type host and wait past debounce.
- Tap Connect.
- Confirm UI reaches connected server view.
- Confirm logcat shows either a prefetch cache hit or safe fallback behavior.
## Acceptance Criteria
- Typing or loading a valid host can warm resolver state before Connect.
- Connect never fails because prefetch failed.
- Connect never uses a prefetched result for a different normalized host.
- Prefetched entries older than 2 minutes are ignored.
- No visible UI is added for prefetch state.
- Bookmarks are not prefetched in bulk.
- Android debug build and focused resolver/Flutter tests pass.
## Implementation Notes
- Prefer a single latest-host cache unless implementation reveals an existing cache abstraction that makes a tiny map simpler.
- Prefer minimal bridge API surface: one prefetch call and connect-time internal cache lookup.
- Keep the resolver cache near existing Rust session/connect code so future non-Flutter clients can benefit from the same behavior.
@@ -0,0 +1,70 @@
# State Sync and UI Settings Validation Design
**Date:** 2026-05-29
**Status:** Approved for implementation
**Scope:** P0/P1 validation-based completion for state-sync evidence and UI settings persistence
## 1. Goal
Close the current DV/P0-P1 gaps for reducer/state-sync evidence and UI settings persistence with tests first, minimal behavior changes, and updated documentation evidence.
## 2. State-Sync Design
`chanora_state` remains the reducer owner. The validation pass adds focused tests for known reducer contracts rather than broad refactoring. Missing behavior is implemented only when a test proves a gap.
Required evidence covers:
| Contract | Evidence |
|---|---|
| Snapshot creates ready state and deterministic normalized order | Existing and expanded reducer tests |
| Reconnect discards stale state and reconnect snapshot replaces state | Existing reducer tests |
| Disconnected/lost states suppress live deltas | Existing reducer tests |
| Duplicate IDs are normalized deterministically | Existing reducer tests |
| Unknown client voice activity is ignored | Existing reducer tests |
| Channel deletion removes clients in deleted channel | New reducer regression test and implementation |
| Same event sequence produces same state and deltas | Existing reducer determinism test |
## 3. UI Settings Design
`UiPreferencesService` remains a Flutter service backed by `shared_preferences`. This is the minimal P0/P1-complete implementation because the current app already uses SharedPreferences and no current behavior requires SQLite-backed UI settings.
`UiSettings` gains a typed `themeMode` field with values:
| Value | Meaning |
|---|---|
| `system` | Follow platform theme |
| `light` | Force light theme |
| `dark` | Force dark theme |
The service persists the selected theme mode, falls back to `system` for invalid stored values, and preserves independent saves for host and nickname.
## 4. App Wiring
`ChanoraApp` becomes stateful enough to load and apply persisted theme mode. `_BetaHome` continues to load/save host and nickname through `UiPreferencesService`. UI controls for selecting theme mode are out of this slice unless already present; this slice provides persistence and app-level application.
## 5. Documentation Updates
After tests pass:
| Document | Update |
|---|---|
| `docs/implementation-status-2026-05-28.md` | Mark reducer scaffold statement stale/resolved and UI settings persistence implemented for SharedPreferences scope |
| `docs/release/dv-waiver-register.md` | Close or soften reducer waiver; keep event replay as P1 gap |
| `docs/verification/swe4-unit-verification-plan.md` | Record reducer test evidence and UI settings tests |
| `docs/verification/swe6-software-verification-plan.md` | Update state sync and UI settings DV status |
| `docs/architecture/sdd.md` | Record UI settings persistence design |
## 6. Validation
Run focused tests:
```text
cargo test -p chanora_state --locked
flutter test test/services/ui_preferences_service_test.dart
```
Run wider checks if touched app-shell behavior requires it:
```text
flutter test --exclude-tags e2e
```
@@ -0,0 +1,266 @@
# Chanora Adaptive 3-Panel Layout Design
**Date:** 2026-06-05
**Status:** Draft
**Scope:** Desktop adaptive layout for ≥1024dp three-panel mode, centralized breakpoint system, and chat panel integration.
---
## 1. Problem Statement
Chanora's current responsive layout uses a single breakpoint (`_wideBreakpoint = 600dp`) scattered across 9 files in 8 duplicatable clusters (5 `LayoutBuilder` sites, 7 `MediaQuery.sizeOf` sites). The desktop layout is a 2-panel split (VoiceBar 320px + SnapshotView flex) with no persistent chat surface.
Research across Discord, Mattermost, Rocket.Chat, Element, and hardware resolution data shows:
- **1024dp** is the industry-standard threshold where a third panel becomes viable (Discord member list, Mattermost RHS, Rocket.Chat contextual bar all use this value).
- At 1024dp, Chanora's math works: `320 + 12 + 300 + 12 + 380 = 1024` — minimum viable for VoicePanel + ChannelTree + ChatPanel.
- Production apps use **push/replace navigation** for chat on constrained widths, reserving persistent panels for ≥1024dp.
- Centralized breakpoint logic is standard practice (Rocket.Chat `LayoutProvider`, Mattermost `WindowSizes`).
## 2. Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| 3-panel activation threshold | **1024dp** | Industry consensus (Discord, Mattermost, Rocket.Chat). Chanora math: center pane = 300dp minimum. |
| Chat behavior at 6001023dp | **Push route (unchanged)** | Research validates current pattern. Overlays are for contextual info, not primary conversation. |
| Chat behavior at ≥1024dp | **Inline panel** | Chat renders in a 380dp right panel alongside the channel tree. No route push. |
| Centralized breakpoints | **New `ChanoraBreakpoints` + `ViewportInfo`** | Replaces 8 duplicated responsive clusters with single source of truth. |
| Architecture approach | **Adaptive Scaffold Shell** | Extends existing widget tree with centralized layout logic. Not a full rewrite. |
| AdaptiveScaffold package | **Not used** | Package discontinued (flutter/flutter#162965). Manual layout gives better control for voice-first UX. |
## 3. Breakpoint System
### 3.1 Layout Classes
Three tiers, aligned with Material 3 adaptive guidance:
| Class | Width Range | Primary Behavior |
|---|---|---|
| `compact` | < 600dp | Single column. VoiceStatusChip at bottom. Chat as pushed route. |
| `medium` | 6001023dp | 2-panel row (VoicePanel 320px + SnapshotView flex). Chat as pushed route. |
| `expanded` | ≥ 1024dp | 3-panel row (VoicePanel 320px + SnapshotView flex + ChatPanel 380px). Chat inline. |
### 3.2 New Files
**`lib/design/breakpoints.dart`** — canonical breakpoint tokens:
```dart
class ChanoraBreakpoints {
static const double compact = 0;
static const double medium = 600;
static const double expanded = 1024;
static const double voicePanelWidth = 320;
static const double chatPanelWidth = 380;
static const double panelGap = 12;
}
enum LayoutClass { compact, medium, expanded }
LayoutClass layoutClassFromWidth(double width) {
if (width >= ChanoraBreakpoints.expanded) return LayoutClass.expanded;
if (width >= ChanoraBreakpoints.medium) return LayoutClass.medium;
return LayoutClass.compact;
}
```
**`lib/design/viewport_info.dart`** — inherited widget that computes layout class once per frame:
```dart
class ViewportInfo extends InheritedWidget {
const ViewportInfo({
super.key,
required this.layoutClass,
required this.width,
required this.height,
required super.child,
});
final LayoutClass layoutClass;
final double width;
final double height;
static ViewportInfo of(BuildContext context) {
final info = context.dependOnInheritedWidgetOfExactType<ViewportInfo>();
assert(info != null, 'No ViewportInfo found in widget tree');
return info!;
}
bool get isCompact => layoutClass == LayoutClass.compact;
bool get isMedium => layoutClass == LayoutClass.medium;
bool get isExpanded => layoutClass == LayoutClass.expanded;
@override
bool updateShouldNotify(ViewportInfo old) =>
layoutClass != old.layoutClass ||
width != old.width ||
height != old.height;
}
```
### 3.3 What This Replaces
The audit identified these duplicatable clusters that get consolidated:
| Cluster | Current Locations | Replacement |
|---|---|---|
| 600dp breakpoint (×3) | `main.dart:304,2280,2468` | `ChanoraBreakpoints.medium` |
| 400px cap (×2) | `connect_widgets.dart`, `voice_settings.dart` | Named token in `ChanoraBreakpoints` |
| 72% modal height (×2) | `audio_output_tile.dart`, `ptt_capability_badge.dart` | Named token |
| 320px voice bar width | `main.dart:2467` | `ChanoraBreakpoints.voicePanelWidth` |
| Platform capability branching | `voice_settings.dart`, `voice_compact.dart`, `audio_processing_config_state.dart` | Centralized capability helper |
## 4. Adaptive Shell
### 4.1 Widget Tree
The existing `_BetaHome` widget tree is restructured to use `ViewportInfo`:
```
_BetaHome
├─ macOS: Scaffold with traffic-light padding (unchanged)
├─ Mobile: ChanoraMobileScaffold (unchanged)
└─ bodyContent:
└─ LayoutBuilder
└─ ViewportInfo (computes layoutClass from constraints)
├─ compact: Column [SnapshotView, VoiceStatusChip, PTT]
├─ medium: Row [VoicePanel, SnapshotView]
└─ expanded: Row [VoicePanel, SnapshotView, ChatPanel]
```
`AdaptiveShell` is a pure layout widget — it reads `ViewportInfo` and composes the appropriate children. All state remains in `_BetaHome`.
### 4.2 Platform Handling
Platform-specific scaffolding stays at the top level, unchanged:
- **macOS**: `Scaffold` with `_macOSTrafficLightPad` top padding (28dp)
- **Mobile**: `ChanoraMobileScaffold` with compact idle chrome
- **Windows/Linux**: Default `Scaffold`
The `ViewportInfo` + layout switch only affects the body content inside the scaffold.
## 5. Chat Panel Behavior
### 5.1 Compact (< 600dp)
No change. Chat opens as a pushed `MaterialPageRoute`:
```
main.dart:_onOpenChat → Navigator.push(ChatPage)
```
Channel tree is fully replaced. Back button returns to main view.
### 5.2 Medium (6001023dp)
Same as compact. Chat is a pushed route. The 2-panel layout (VoicePanel + SnapshotView) stays as the home screen.
### 5.3 Expanded (≥ 1024dp)
Chat renders inline in a 380dp right panel. The flow:
1. User taps "Open Text Chat" on a client, or taps the chat badge
2. `_onOpenChat` reads `ViewportInfo.of(context).isExpanded`
3. If expanded: sets `_inlineChatTarget` state → `ChatPanel` renders in the third column
4. If not expanded: pushes `ChatPage` route (unchanged behavior)
### 5.4 ChatPanel Widget
New widget for ≥1024dp only:
```
ChatPanel (380dp fixed width)
├─ Header: target name + close button
├─ Message list (scrollable, max-width ~500dp for readability)
└─ Input field
```
**State sharing:** The `_chatMessages` list and `_chatFeedRevision` listenable in `_BetaHome` already track all messages. `ChatPanel` reads from the same source — no duplication.
**Close behavior:** User taps close button → `_inlineChatTarget` set to null → `ChatPanel` removed from tree.
### 5.5 Width Transition
When the user resizes from ≥1024dp to <1024dp while chat is open inline:
1. `ChatPanel` disappears (it's only in the expanded layout branch)
2. A brief snackbar appears: "Tap the chat button to continue your conversation"
3. The `_inlineChatTarget` state is preserved — tapping the chat button reopens the pushed `ChatPage` route with the same target
This matches Discord's behavior when the member list collapses on resize.
## 6. Panel Sizing
| Element | Width | Behavior |
|---|---|---|
| VoicePanel (left) | 320dp fixed | VoiceBar, connection status, PTT controls. Unchanged. |
| Panel gaps | 12dp | Between each panel. Unchanged. |
| SnapshotView (center) | flex (1fr) | Grows to fill remaining space. |
| ChatPanel (right) | 380dp fixed | Only rendered at ≥1024dp. |
| Chat messages | max-width ~500dp | Centered within ChatPanel for readability. |
| macOS traffic light pad | 28dp top | Unchanged. Only affects height. |
**Center pane widths at common viewports:**
| Viewport | Center Width | Feel |
|---|---|---|
| 1024dp | 300dp | Minimum viable (matches Discord at same width) |
| 1200dp | 476dp | Comfortable |
| 1280dp | 556dp | Spacious (Chanora's default window size) |
| 1440dp | 716dp | Very spacious |
| 1920dp | 1184dp | Ultra-wide — consider capping center max-width post-MVP |
## 7. Migration Map
| File | Change | Scope |
|---|---|---|
| `lib/design/breakpoints.dart` | **New** — breakpoint tokens + `LayoutClass` enum | New file |
| `lib/design/viewport_info.dart` | **New**`ViewportInfo` inherited widget | New file |
| `lib/main.dart` | Replace `_wideBreakpoint = 600.0` with `ChanoraBreakpoints.medium`. Wrap body in `ViewportInfo`. Add `_inlineChatTarget` state. Branch `_onOpenChat` for expanded vs compact/medium. Add `ChatPanel` to expanded Row. | Significant |
| `lib/widgets/chat_views.dart` | Replace `_chatMobileBreakpoint` with `ChanoraBreakpoints.medium`. No structural changes. | Token swap |
| `lib/widgets/connect_widgets.dart` | Replace hardcoded 400px with token. | Token swap |
| `lib/widgets/app_snack_bar.dart` | Replace hardcoded 600/560px with tokens. | Token swap |
| `lib/widgets/snapshot_view.dart` | No changes. Local spacer math stays local. | None |
| `lib/widgets/voice_compact.dart` | Replace platform branching with centralized helper (optional, post-MVP). | Optional |
**Unchanged:** macOS scaffold, ChanoraMobileScaffold, all voice controls, channel tree, chat route for compact/medium, all Rust bridge code.
## 8. Hardware Coverage
The 1024dp threshold coverage based on 2026 resolution data:
| Setup | Logical Width | Sees 3-Panel? |
|---|---|---|
| 1920×1080 @100% fullscreen | 1920dp | Yes |
| 1920×1080 @125% fullscreen | 1536dp | Yes |
| 1920×1080 @150% fullscreen | 1280dp | Yes |
| 1366×768 @100% fullscreen | 1366dp | Yes |
| 1366×768 @125% fullscreen | 1093dp | Yes |
| 1366×768 @125% windowed (~85%) | ~930dp | No (2-panel) |
| 2560×1440 @100% half-screen | ~1280dp | Yes |
| 2560×1440 @125% half-screen | ~1024dp | Yes (edge) |
| MacBook 13" Split View | ~708dp | No (2-panel) |
| MacBook 14" Split View | ~744dp | No (2-panel) |
| MacBook 16" Split View | ~852dp | No (2-panel) |
Chanora's default window (1280×720 on Windows/Linux) starts in 3-panel mode immediately.
## 9. Out of Scope (Post-MVP)
- Resizable panels (drag-to-resize VoicePanel/ChatPanel width)
- NavigationRail for ultra-wide monitors
- ChatPanel showing user profile or channel info
- Center pane max-width cap for ultra-wide monitors
- Centralized platform capability helper (consolidating voice_settings/voice_compact/audio_processing branching)
- Animated transitions between layout classes
- ChatPanel as a sheet/drawer on medium widths
## 10. References
- Discord member list collapse at 1024px: [compact-discord](https://github.com/asportnoy/compact-discord)
- Mattermost RHS persistent at ≥1024px: [structure.scss](https://github.com/mattermost/mattermost/blob/3440453d82613b1d8d67c93011c11d56a1380869/webapp/channels/src/sass/base/_structure.scss)
- Rocket.Chat contextual bar persistent at ≥1024px (lg breakpoint): [fuselage-tokens](https://github.com/RocketChat/fuselage/blob/ed91cb04db9fd6c35b43390190cbf7327c3eab9e/packages/fuselage-tokens/src/breakpoints.jsonc)
- Flutter AdaptiveScaffold discontinued: [flutter/flutter#162965](https://github.com/flutter/flutter/issues/162965)
- Material 3 canonical breakpoints: [m3.material.io/foundations/layout](https://m3.material.io/foundations/layout/breakpoints/overview)
- Chanora adaptive layout policy: [docs/ui-ux/adaptive-layout-platform-guide.md](../ui-ux/adaptive-layout-platform-guide.md)
@@ -0,0 +1,160 @@
# Maintainability Continuation Design
**Date:** 2026-06-08
**Status:** Approved design for implementation and full code-review remediation; Task 0 and focused audio-realtime fixes landed, documentation/governance alignment in progress
**Scope:** Continue the current working-branch maintainability pass, add full code-review findings, and fix high-risk bugs before broad rewrites.
## Purpose
This design continues the project review already present in the working tree. The goal is to simplify the project where changes are low-risk, testable, and documented, while avoiding speculative architecture churn.
The work covers unnecessary functions, structs, files, modules, duplicated custom implementations, built-in replacement opportunities, outdated documents, fail-safe gaps, Android runtime verification requirements, and full code-review remediation for hidden bugs.
## Recommended Approach
Use a targeted continuation of the current maintainability pass, now ordered by safety risk.
The existing branch already contains a first slice of simplification: core event DTO extraction, network diagnostics locality, `VecDeque` queue improvements, derived PTT backend errors, render downmix helper reuse, state reducer reuse, workspace metadata cleanup, and documentation updates. This design treats those changes as the baseline, but the full review found privacy, realtime-audio, disconnect, and documentation-governance issues that take priority over cosmetic simplification.
The remediation order is:
- Privacy and stuck-transmit fail-safes in Flutter voice state. Fixed in commit `d835394`.
- iOS audio-session error hardening before Rust VoiceProcessingIO startup. Missing-plugin/error handling fixed in commit `d835394`; iOS device runtime verification remains required.
- Rust realtime audio safety, especially unsynchronized render-reference buffers and blocking/allocating callbacks. Focused callback-path hardening fixed in commit `8606eb4`; full lock-free `AudioHandler` / config / debug-recorder redesign remains a follow-up.
- Bounded disconnect/control-plane progress in Rust protocol/core. Pending unless later code-review evidence closes it.
- Documentation and release/governance contradictions that can cause wrong verification claims. Addressed by Task 0.2 documentation alignment.
- Larger Module splits after behavior is protected by tests.
Rejected alternatives:
- Documentation-only audit: safer, but leaves clear simplifications unimplemented.
- Broad architectural cleanup: may produce long-term wins, but is too risky for this pass because bridge, protocol, audio, and Android behavior have high regression cost.
## Architecture Boundaries
The existing responsibilities remain intact:
- Flutter owns presentation, navigation, Material 3 behavior, accessibility, localization presentation, and platform UI behavior.
- Flutter Rust Bridge owns typed DTO/API glue and generated bindings.
- Rust Core owns session orchestration, cross-crate coordination, bridge-facing public events, and stable public APIs.
- Protocol owns TeamSpeak-compatible protocol isolation behind `tsclientlib`.
- Audio owns capture, render, processing, PTT backends, platform audio behavior, and voice packet handling where explicitly documented.
- Diagnostics owns redaction, logs, export records, and bounded diagnostic history.
Public interfaces should stay stable unless a change clearly removes duplicated or unnecessary code and has direct verification.
## Review Targets
The implementation review should inspect these areas first:
- `core/chanora_core/src/lib.rs`, `events.rs`, `network_diagnostics.rs`, and `ptt.rs`
- `crates/chanora_audio`, especially duplicated render, capture, PTT, and platform-audio helpers
- `crates/chanora_state` reducer paths
- `crates/chanora_protocol` adapter ordering, event, and DTO mapping paths
- `crates/chanora_bridge/src/api.rs`, excluding generated bridge files unless regeneration is intentionally part of a change
- `crates/chanora_diagnostics/src/lib.rs`
- `crates/chanora_prefetch` and `crates/chanora_resolver` as a documented follow-up seam decision unless a trivial cleanup appears
- `apps/chanora_flutter/lib`, excluding generated localization and bridge files unless an API change requires updates
- governance, architecture, implementation-status, and verification documents affected by the code review
Full code-review remediation targets:
- `apps/chanora_flutter/lib/main.dart`: mute ownership, iOS audio-session preflight, chat/unread follow-ups, and oversized session-controller extraction candidates.
- `apps/chanora_flutter/lib/widgets/voice_compact.dart`: touch PTT release-on-dispose fail-safe.
- `apps/chanora_flutter/lib/services/ios_audio_session_controller.dart`: missing-plugin fail-safe handling.
- `apps/chanora_flutter/lib/services/audio_lifecycle_service.dart`: macOS route/default-device no-op documentation or future adapter seam.
- `crates/chanora_audio/src/android_voice_unit.rs`, `ios_raw_unit.rs`, `ios_voice_unit.rs`, and `engine.rs`: realtime callback safety and platform lifecycle rollback.
- `core/chanora_core/src/lib.rs` and `crates/chanora_protocol/src/adapter.rs`: bounded disconnect and control-plane progress under voice load.
- `README.md`, `CHANGELOG.md`, `docs/release/*`, `docs/verification/*`, and `docs/governance/product-decision-register.md`: stale platform, release, VAD, Android runtime, and decision-register claims.
## Simplification Rules
Every code change must satisfy these rules:
- Prefer deletion, built-in APIs, derives, or reuse of existing helpers over new abstractions.
- Merge files or modules only when the merged unit has a clearer single responsibility.
- Split files only when it improves locality around a stable responsibility and preserves public API shape.
- Do not manually edit generated files unless the generation process is part of the verified change.
- Do not introduce backward-compatibility shims unless there is a persisted-data, shipped-API, external-consumer, or explicit product need.
- Record larger architectural opportunities in the maintainability review instead of forcing them into this pass.
Full code-review fix rules:
- Fix safety bugs before Module split work.
- Use test-driven development for production behavior changes: write the failing test, run it, implement the minimal fix, then rerun the test.
- Keep manual/generated bridge files out of direct edits unless regeneration is intentionally verified.
- Split huge Modules only when the split creates a deeper Module with leverage and locality; file-size-only sharding is not sufficient.
- Compare architecture choices against established voice/chat client practice: Mumble-style bounded voice/control separation, Discord/TeamSpeak-style independent mute owners, WebRTC-style realtime callback minimalism, and Matrix/Element-style coherent state replication.
## Testing Design
Verification is tied to change type:
- Rust-only changes require `cargo fmt --all`, `cargo check --workspace`, and `cargo test --workspace`.
- Flutter changes require `flutter analyze` and `flutter test --exclude-tags e2e` from `apps/chanora_flutter`.
- Bridge DTO/API changes require Rust verification, bridge generation check, Flutter analyze, and Flutter tests.
- Android platform, permission, lifecycle, or audio changes require Rust and Flutter verification plus Android NDK target compilation, `adb devices -l`, Android build/install, and a device or emulator smoke test.
- Documentation-only changes require affected docs and cross-links to be read and checked; code tests are not required unless the docs describe a code change just made.
If no ADB target is connected, Android runtime verification must be recorded as blocked. If Android target compilation cannot find the NDK compiler, for example `aarch64-linux-android-clang`, Android build evidence must also be recorded as blocked. The implementation must not claim Android runtime success without build/install/smoke evidence from an authorized device or emulator.
## Fail-Safe Review
The review must identify fail-safe gaps and either verify them, fix them, or record the missing evidence.
Priority fail-safe areas:
- User mute ownership must not be cleared by talk-power or permission recovery.
- Touch and keyboard PTT must release on cancellation, disposal, disconnect, lifecycle transition, or missed-up conditions.
- iOS AVAudioSession must be configured and activated before VoiceProcessingIO startup.
- Realtime callbacks must not block, allocate repeatedly, or use unsynchronized mutable aliasing.
- Disconnect and control requests must be bounded and must not hold global session locks across unbounded transport waits.
- Android and iOS device runtime behavior must be verified on hardware or an authorized emulator/simulator where applicable before platform success is claimed.
- Android secure storage and Keystore-backed data-encryption-key handling
- Android permission and audio lifecycle behavior
- Stuck PTT prevention and missed-key-up recovery
- Diagnostic redaction and privacy-sensitive event export
- Bridge DTO drift between Core, Bridge, and Dart generated bindings
- Protocol isolation exceptions for voice packet handling
- Runtime behavior gaps not covered by unit tests
No release-readiness or production-safety claim should be made without matching evidence.
## Documentation Design
The working review record remains `docs/governance/maintainability-review-2026-06-08.md`.
Documents to update when affected:
- `README.md`
- `CHANGELOG.md`
- `docs/governance/document-index.md`
- `docs/governance/product-decision-register.md`
- `docs/architecture/sad.md`
- `docs/architecture/sdd.md`
- `docs/implementation-status-2026-05-28.md`
- `docs/verification/swe4-unit-verification-plan.md`
- `docs/verification/swe5-software-integration-verification-plan.md`
- `docs/verification/verification-master-plan.md`
- `docs/verification/sys4-system-integration-verification-plan.md`
- `docs/release/release-readiness-go-nogo-record.md`
- `docs/release/dv-waiver-register.md`
- release or fail-safe records if verification status changes
Documentation should distinguish completed changes, follow-up opportunities, blocked verification, and release limitations.
## Commit Policy
No commit is created automatically. A commit happens only when explicitly requested, after inspecting `git status`, `git diff`, and recent commits.
## Success Criteria
This work is successful when:
- Safe simplifications are implemented or recorded as follow-up opportunities.
- Built-in replacement opportunities are applied only when behavior remains covered by tests.
- Fail-safe gaps are documented with required evidence or fixed with verification.
- Rust and Flutter verification are run as required by the touched files, including targeted regression tests for every fixed bug.
- Android ADB runtime verification is run when a target is available or explicitly recorded as blocked.
- Documents reflect the final code and verification state.
- Full code-review findings are either fixed, downgraded with evidence, or recorded as follow-up risks with verification requirements.
@@ -0,0 +1,176 @@
# Poke Without Message Design
**Date:** 2026-06-09
**Status:** Approved design for implementation
**Scope:** Allow intentional TeamSpeak-compatible pokes without message text while preserving empty-message blocking for normal chat targets.
## 1. Goal
Chanora should let a user poke another connected client without typing a message. A poke is an attention event, not an empty chat message. The UI should make that distinction explicit so the empty state is intentional, understandable, and safe from accidental spam.
The implementation target is narrow:
- Sending a poke with an empty message is allowed.
- Sending an empty normal chat message remains blocked.
- Incoming and historical empty pokes continue to render as poke events, not blank chat bubbles.
- Existing poke notification behavior remains compatible with message and no-message pokes.
## 2. Research Summary
TeamSpeak-compatible poke behavior is command-like: the ServerQuery shape is `clientpoke clid={clientID} msg={text}`, backed by poke permissions such as `i_client_poke_power` and `i_client_needed_poke_power`. The product semantics are closer to an attention nudge than to a private text message.
Client behavior and community expectations point to two UX risks:
- The action can be useful without text because the sender often only wants attention.
- The action can be abused as interruption spam, so the UI must keep the action deliberate and preserve existing receiver-side suppression and notification preferences.
The approved product direction is therefore to model no-message poke as a first-class attention event with optional text, rather than as an exception in the normal chat composer.
## 3. Recommended UX
Poke uses a poke-specific sending surface. The surface may reuse the current chat detail implementation internally, but the user-facing copy and validation must make the target type clear.
Required poke-target behavior:
| Element | Behavior |
|---|---|
| Header | Shows that the current surface is for poking the selected user. |
| Text field | Optional message input. Placeholder should communicate that the message is optional. |
| Primary action | Label is `Poke`, not `Send`. Enabled even when the trimmed message is empty. |
| Empty send | Sends an intentional poke with `message: ''`. |
| Non-empty send | Sends a poke with the typed message. |
| History row | Empty poke renders as an attention event such as `Alice poked you`, never as a blank message. |
Required non-poke chat behavior:
| Target | Empty text behavior |
|---|---|
| Channel chat | Block send. |
| Server chat | Block send. |
| Private chat | Block send. |
| Any future text-chat target | Block send unless it is explicitly modeled as a poke-like attention event. |
## 4. Architecture Boundaries
The change should stay inside the existing UI and bridge boundaries:
- Flutter owns presentation, composer validation, button enablement, localization copy, and widget tests.
- Flutter Rust Bridge continues to pass typed `BridgeMessageTarget` and message text across the bridge.
- Rust Core and Protocol continue to route `MessageTarget::Poke(client_id)` through the existing poke send path.
- Protocol remains the only layer that knows how `tsclientlib` sends a TeamSpeak-compatible poke.
No new protocol concept is required. The existing bridge/protocol model already has `BridgeMessageTarget.poke` / `MessageTarget::Poke(u64)` and `client.poke(message)`. The key design change is target-aware composer validation in Flutter.
## 5. Implementation Design
The implementation should use a target-aware send policy.
For `BridgeMessageTarget.poke`:
- Do not reject an empty trimmed input.
- Send the original or trimmed message according to the existing chat composer convention. If the current send path trims normal messages before sending, apply the same text normalization before passing the poke message.
- Clear the composer after successful send, including empty-poke sends.
- Preserve existing error and snackbar behavior for failed sends.
For all other `BridgeMessageTarget` variants:
- Keep the existing empty-trimmed-text guard.
- Keep current button enablement and keyboard submit behavior unless those paths need target-aware adjustment to preserve the same empty-message block.
A simple policy helper is preferred over scattered conditionals. Example shape:
```dart
bool canSendMessage({
required BridgeMessageTarget target,
required String text,
}) {
if (target is BridgeMessageTarget_Poke) {
return true;
}
return text.trim().isNotEmpty;
}
```
The exact Dart type checks should follow the generated bridge type names used in the current codebase.
## 6. Notification And History Behavior
Existing no-message receiving behavior should remain the reference behavior:
- Incoming empty poke notification body falls back to text equivalent to `Alice pokes you`.
- Incoming poke with message includes the message in the notification body.
- Active-chat suppression and muted-sender preferences continue to apply.
- Poke history rows distinguish poke events from normal chat rows.
The send-side change must not introduce a new blank message row shape. If the sender's local history records sent pokes, empty poke history should render as a poke action line with no empty bubble.
## 7. Abuse And Safety Rules
This slice does not add new anti-spam controls. It relies on existing TeamSpeak-compatible permissions, inbound poke strength/rate suppression, notification preferences, active-chat suppression, and muted sender handling.
The implementation must not weaken any existing receiver-side controls. If testing reveals that empty sent pokes bypass suppression, notification preferences, or history classification, that is a bug to fix in the same implementation pass.
Future follow-ups, not part of this slice:
- Per-sender or per-server outbound poke cooldown UI.
- Receiver-side "never show poke dialog" equivalent beyond current notification preferences.
- Dedicated poke inbox or grouped poke history.
## 8. Files Expected To Change
Expected implementation targets:
| File | Expected change |
|---|---|
| `apps/chanora_flutter/lib/widgets/chat_views.dart` | Make composer validation and action enablement target-aware for poke. Update poke placeholder/action copy if needed. |
| `apps/chanora_flutter/test/widgets/chat_views_test.dart` | Add widget coverage for empty poke send and normal empty chat blocking. |
Optional targets if the implementation exposes missing copy or routing seams:
| File | Possible change |
|---|---|
| `apps/chanora_flutter/lib/main.dart` | Only if opening a poke target needs a clearer poke-specific title or route configuration. |
| `apps/chanora_flutter/lib/l10n/*.arb` | Only if current copy cannot express optional poke messages without hard-coded strings. |
| `apps/chanora_flutter/test/services/poke_notification_service_test.dart` | Only if send-side changes affect notification payload assumptions. |
The Rust protocol path should not need behavior changes unless tests prove that empty strings are blocked below Flutter.
## 9. Test Design
Required tests:
- Poke target shows an enabled primary `Poke` action when the text field is empty.
- Tapping `Poke` on an empty poke target calls the send callback with `BridgeMessageTarget.poke` and an empty message.
- Poke target still sends a typed message when text is present.
- Normal channel/server/private chat targets keep blocking empty sends.
- Empty poke history renders as a poke event line, not an empty text bubble.
Useful regression checks if already easy to target:
- Keyboard submit follows the same target-aware validation as the button.
- Failed empty-poke send keeps existing error presentation.
- Incoming empty poke notification tests still pass unchanged.
## 10. Validation
For the implementation branch, run focused Flutter verification first:
```text
flutter test test/widgets/chat_views_test.dart
flutter test test/services/poke_notification_service_test.dart
flutter test test/services/poke_active_chat_test.dart
flutter analyze
```
If Rust or bridge files are touched, also run the matching Rust and bridge checks for the touched layer. Documentation-only changes require reading the affected spec and checking the diff; code tests are not required for this design commit.
## 11. Success Criteria
This design is implemented successfully when:
- A user can send a poke with no typed message.
- Normal chat targets still reject empty sends.
- The poke composer communicates that message text is optional.
- Empty pokes are represented as poke events in history and notifications.
- Existing poke notification preferences and suppression behavior remain intact.
- Focused widget/service tests and `flutter analyze` pass, or any unrelated pre-existing failure is named with evidence.
@@ -0,0 +1,472 @@
# Documentation Site & ASPICE Traceability System Design
**Date:** 2026-06-13
**Status:** Approved design for implementation
**Scope:** Docusaurus doc site, git submodule separation, tag-based ASPICE traceability with custom validation plugin, Cloudflare Pages hosting with access control
## 1. Purpose
Replace the current flat markdown documentation tree with a browseable, searchable, access-controlled doc site that serves three audiences: developers, ASPICE assessors, and non-technical stakeholders. Introduce automated traceability enforcement that validates the ASPICE requirement chain on every build.
## 2. Current State
- 65+ markdown files in `docs/` with no sidebar, no search, no visual hierarchy
- ASPICE traceability maintained in manual markdown tables (`traceability-matrix.md`)
- Cross-references are backtick-quoted paths in prose, not clickable links
- Link coverage report found 2 broken links + 5 broken path references
- No CI enforcement of traceability integrity
- No access control — docs only viewable via GitHub repo browsing or local clone
## 3. Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Repo structure | Git submodule (`docs/``chanora-docs` repo) | Cleaner separation, access control, CI independence, separate versioning |
| Doc site generator | Docusaurus | Meta-maintained, full plugin API, built-in tags and versioning, active ecosystem |
| Traceability mechanism | Tag-based + custom Docusaurus plugin | Tags for browsing, plugin for automated chain validation and coverage reports |
| Hosting | Cloudflare Pages | Free tier, global CDN, auto-deploy from CI |
| Access control | Cloudflare Access | Free for up to 50 users, email-based auth, SSO support |
| Code path references in docs | Remove from ASPICE docs, move to `impl-mapping.md` | ASPICE traces requirement IDs, not file paths. Code paths are developer convenience |
| Provenance records | Deferred | Completed ASPICE-related plans archived in `dev-docs/superpowers/plans/_archived/` for now |
## 4. Repo Structure
### 4.1 Docs submodule (`chanora-docs` repo)
```
chanora-docs/
├── mkdocs.yml
├── requirements.txt
├── pyproject.toml
├── docs/
│ ├── index.md
│ ├── .meta.yml
│ ├── requirements/
│ │ ├── .meta.yml
│ │ ├── sysrs.md
│ │ ├── sysdes.md
│ │ └── srs.md
│ ├── architecture/
│ │ ├── .meta.yml
│ │ ├── sad.md
│ │ ├── sdd.md
│ │ ├── file-transfer-design.md
│ │ ├── file-transfer-research.md
│ │ ├── file-transfer-implementation-plan.md
│ │ └── desktop-ptt-architecture.md
│ ├── verification/
│ │ ├── .meta.yml
│ │ ├── verification-master-plan.md
│ │ ├── swe4-unit-verification-plan.md
│ │ ├── swe5-software-integration-verification-plan.md
│ │ ├── swe6-software-verification-plan.md
│ │ └── sys4-system-integration-verification-plan.md
│ ├── governance/
│ │ ├── .meta.yml
│ │ ├── document-index.md
│ │ ├── traceability-matrix.md
│ │ ├── product-decision-register.md
│ │ ├── baseline-approval-record.md
│ │ ├── baseline-candidate-validation-report.md
│ │ ├── document-review-report.md
│ │ ├── document-naming-convention.md
│ │ ├── decision-impact-assessment.md
│ │ ├── git-commit-message-convention.md
│ │ ├── repo-format-validation-report.md
│ │ ├── path-migration-map.md
│ │ └── maintainability-review-2026-06-08.md
│ ├── security/
│ │ ├── .meta.yml
│ │ ├── security-privacy-legal-guideline.md
│ │ ├── threat-model.md
│ │ ├── secure-storage-audit-report.md
│ │ ├── diagnostic-redaction-audit-report.md
│ │ ├── dependency-and-supply-chain-report.md
│ │ ├── license-inventory.md
│ │ └── flutter-license-inventory.md
│ ├── privacy/
│ │ └── privacy-policy.md
│ ├── legal/
│ │ └── trademark-and-attribution-review.md
│ ├── release/
│ │ ├── platform-release-policy.md
│ │ ├── release-readiness-go-nogo-record.md
│ │ └── dv-waiver-register.md
│ ├── references/
│ │ ├── aspice-swe2-swe3-integration-note.md
│ │ ├── external-references.md
│ │ ├── yatqa-en.md (moved from offline-knowledge/external/)
│ │ ├── yatqa-de.md (moved from offline-knowledge/external/)
│ │ ├── teaspeak-overview.md (moved from offline-knowledge/external/)
│ │ └── respeak-overview.md (moved from offline-knowledge/external/)
│ ├── ui-ux/
│ │ ├── material3-guideline.md
│ │ ├── material3-design-tokens.md
│ │ ├── material3-component-catalog.md
│ │ └── adaptive-layout-platform-guide.md
│ ├── i18n/
│ │ └── localization-architecture.md
│ └── tags.md
├── plugins/
│ └── traceability/
│ ├── __init__.py
│ └── traceability.py
├── scripts/
│ └── validate_traceability.py
├── .github/
│ └── workflows/
│ └── deploy.yml
├── wrangler.toml
└── README.md
```
### 4.2 Code repo local files
```
chanora/
├── docs/ → chanora-docs (submodule)
├── dev-docs/
│ ├── superpowers/
│ │ ├── specs/
│ │ │ ├── 2026-05-28-server-resolution-prefetch-design.md
│ │ │ ├── 2026-05-28-chanora-server-prefetch-crate-design.md
│ │ │ ├── 2026-05-29-state-sync-ui-settings-validation-design.md
│ │ │ ├── 2026-06-05-adaptive-3-panel-layout-design.md
│ │ │ ├── 2026-06-08-maintainability-continuation-design.md
│ │ │ ├── 2026-06-09-poke-without-message-design.md
│ │ │ └── 2026-06-13-documentation-site-design.md
│ │ └── plans/
│ │ ├── _archived/
│ │ │ ├── 2026-05-29-finish-dv-document-tree.md
│ │ │ ├── 2026-05-29-dv-evidence-pack.md
│ │ │ ├── 2026-05-29-swe2-swe3-baselines.md
│ │ │ └── 2026-05-29-state-sync-ui-settings-validation.md
│ │ ├── 2026-05-28-server-resolution-prefetch.md
│ │ ├── 2026-05-28-chanora-server-prefetch-crate.md
│ │ ├── 2026-06-06-chat-panel-switching.md
│ │ ├── 2026-06-08-core-internal-split.md
│ │ └── 2026-06-08-maintainability-continuation.md
│ ├── offline-knowledge/
│ │ ├── coverage-analysis.md
│ │ ├── doc-quality-analysis.md
│ │ ├── link-coverage-report.md
│ │ └── reviews/
│ ├── implementation-status-2026-05-28.md
│ ├── release/ios-build.md
│ └── impl-mapping.md
├── apps/, crates/, core/
├── AGENTS.md
└── README.md
```
## 5. Docusaurus Configuration
### 5.1 Site configuration (`docusaurus.config.js`)
```js
module.exports = {
title: 'Chanora Engineering Docs',
tagline: 'ASPICE-compliant engineering documentation with automated traceability',
url: 'https://docs.chanora.dev',
baseUrl: '/',
organizationName: 'chanoraapp',
projectName: 'docs',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
i18n: { defaultLocale: 'en', locales: ['en'] },
themes: ['@docusaurus/theme-classic'],
plugins: [
'./plugins/traceability',
],
themeConfig: {
navbar: {
title: 'Chanora Docs',
items: [
{ type: 'doc', position: 'left', label: 'Requirements', docId: 'requirements/sysrs' },
{ type: 'doc', position: 'left', label: 'Architecture', docId: 'architecture/sad' },
{ type: 'doc', position: 'left', label: 'Verification', docId: 'verification/verification-master-plan' },
{ type: 'doc', position: 'left', label: 'Governance', docId: 'governance/document-index' },
{ type: 'doc', position: 'left', label: 'Security', docId: 'security/security-privacy-legal-guideline' },
{ type: 'doc', position: 'left', label: 'Release', docId: 'release/platform-release-policy' },
{ type: 'doc', position: 'left', label: 'References', docId: 'references/external-references' },
{ type: 'doc', position: 'left', label: 'UI/UX', docId: 'ui-ux/material3-guideline' },
{ type: 'tags' },
],
},
footer: {
style: 'dark',
links: [
{ title: 'Docs', items: [
{ label: 'Requirements', to: '/docs/requirements/sysrs' },
{ label: 'Architecture', to: '/docs/architecture/sad' },
{ label: 'Verification', to: '/docs/verification/verification-master-plan' },
]},
{ title: 'Governance', items: [
{ label: 'Traceability Matrix', to: '/docs/governance/traceability-matrix' },
{ label: 'Decision Register', to: '/docs/governance/product-decision-register' },
{ label: 'Document Index', to: '/docs/governance/document-index' },
]},
],
},
prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula },
},
};
```
### 5.2 Sidebar (`sidebars.js`)
```js
module.exports = {
requirements: [
'requirements/sysrs',
'requirements/sysdes',
'requirements/srs',
],
architecture: [
'architecture/sad',
'architecture/sdd',
'architecture/file-transfer-design',
'architecture/file-transfer-research',
'architecture/file-transfer-implementation-plan',
'architecture/desktop-ptt-architecture',
],
verification: [
{
type: 'category',
label: 'System Level',
items: ['verification/sys4-system-integration-verification-plan'],
},
{
type: 'category',
label: 'Software Integration',
items: ['verification/swe5-software-integration-verification-plan'],
},
{
type: 'category',
label: 'Unit Level',
items: ['verification/swe4-unit-verification-plan'],
},
{
type: 'category',
label: 'Software Qualification',
items: ['verification/swe6-software-verification-plan'],
},
'verification/verification-master-plan',
],
governance: [
'governance/document-index',
'governance/traceability-matrix',
'governance/product-decision-register',
'governance/baseline-approval-record',
'governance/baseline-candidate-validation-report',
'governance/document-review-report',
'governance/document-naming-convention',
'governance/decision-impact-assessment',
'governance/git-commit-message-convention',
'governance/repo-format-validation-report',
'governance/path-migration-map',
'governance/maintainability-review-2026-06-08',
],
security: [
'security/security-privacy-legal-guideline',
'security/threat-model',
'security/secure-storage-audit-report',
'security/diagnostic-redaction-audit-report',
'security/dependency-and-supply-chain-report',
'security/license-inventory',
'security/flutter-license-inventory',
'privacy/privacy-policy',
'legal/trademark-and-attribution-review',
],
release: [
'release/platform-release-policy',
'release/release-readiness-go-nogo-record',
'release/dv-waiver-register',
],
references: [
'references/external-references',
'references/aspice-swe2-swe3-integration-note',
'references/yatqa-en',
'references/yatqa-de',
'references/teaspeak-overview',
'references/respeak-overview',
],
uiux: [
'ui-ux/material3-guideline',
'ui-ux/material3-design-tokens',
'ui-ux/material3-component-catalog',
'ui-ux/adaptive-layout-platform-guide',
'i18n/localization-architecture',
],
};
```
## 6. Tag-Based Traceability
### 6.1 Front matter schema
Every document includes YAML front matter:
```yaml
---
tags: [swe.2, architecture, SRS-003, SRS-008, SRS-016]
upstream: [srs, sysdes] # Custom metadata for traceability plugin
downstream: [sdd, swe4, swe5] # Custom metadata for traceability plugin
lifecycle: SWE.2 # Custom metadata for traceability plugin
status: baseline # Custom metadata for traceability plugin
---
```
The `tags` field is consumed by the MkDocs Material tags plugin for browsing. The `upstream`, `downstream`, `lifecycle`, and `status` fields are custom metadata consumed by the traceability plugin for chain validation.
### 6.2 Tag categories
| Tag pattern | Purpose | Example |
|---|---|---|
| `swe.1` through `swe.6`, `sys.4` | ASPICE lifecycle stage | Every doc gets at least one |
| `sysrs`, `sysdes`, `srs`, `sad`, `sdd` | Document type | Identifies the doc in the chain |
| `requirements`, `architecture`, `verification`, `governance` | Section category | For filtering |
| `SysRS-233`, `SRS-045`, `SDD-MOD-009` | Requirement/module IDs | Traceability links |
| `baseline`, `draft`, `candidate` | Document status | Assessor visibility |
| `dec-012`, `dec-020` | Decision register refs | Cross-ref to governance |
### 6.3 Section defaults via `.meta.yml`
```yaml
# docs/verification/.meta.yml
tags: [verification]
status: candidate
```
### 6.4 Verification page trace mappings
| Verification plan | Upstream traces | Tags |
|---|---|---|
| SYS.4 System Integration | SysDes, SysRS | `[sys.4, verification, SysDes-102, SysDes-103, ...]` |
| SWE.5 Software Integration | SAD (SWE.2) | `[swe.5, verification, sad-component-bridge, ...]` |
| SWE.4 Unit Verification | SDD (SWE.3) | `[swe.4, verification, SDD-MOD-001, ...]` |
| SWE.6 Software Verification | SRS | `[swe.6, verification, SRS-128, ...]` |
## 7. Custom Traceability Plugin
### 7.1 Location
`plugins/traceability/traceability.py` — MkDocs plugin, ~200 lines Python.
### 7.2 Behavior
On `on_page_markdown` event:
- Scan each page for requirement ID patterns: `SysRS-\d+`, `SysDes-\d+`, `SRS-\d+`, `SDD-MOD-\d+`, `DEC-\d+`
- Build an in-memory traceability graph: upstream ID → downstream document → verification plan
On `on_post_build` event:
- Validate every requirement ID referenced downstream exists in its source document
- Validate every upstream document ID has at least one downstream allocation
- Flag orphaned references (IDs mentioned but never defined)
- Verify bidirectional completeness
### 7.3 Outputs
- `traceability-coverage.json` — machine-readable coverage report with chain completeness percentages
- Console output with pass/fail summary
- Traceability dashboard page with coverage table and broken chain details
- Build failure (`sys.exit(1)`) on broken chains when `strict: true`
### 7.4 Standalone CI validator
`scripts/validate_traceability.py` — same validation logic, runnable without MkDocs build:
```
python scripts/validate_traceability.py docs/
```
Exit code 0 = all chains valid. Exit code 1 = broken chains with details on stderr.
## 8. Hosting & Deployment
### 8.1 Architecture
```
chanora-docs repo → push to main → GitHub Actions
→ validate_traceability.py
→ mkdocs build --strict
→ Cloudflare Pages (via Wrangler)
→ Cloudflare Access policy (email-based auth)
```
### 8.2 CI workflow
On pull request: build + validate only (no deploy).
On push to main: build + validate + deploy to Cloudflare Pages.
### 8.3 Cloudflare Access policy
- Free tier for up to 50 users
- Email-based authentication with optional Google/GitHub SSO
- One-time PIN for external assessors
- Access rules: allow company emails, specific assessor emails; block all others
## 9. Migration Plan
### 9.1 Code path reference cleanup
SAD and SDD currently list file paths (`crates/chanora_protocol/src/`) in component tables. These references will be:
- Replaced with component/module IDs only in the docs submodule
- Preserved in `dev-docs/impl-mapping.md` in the code repo for developer convenience
### 9.2 File moves
| From (code repo) | To | Action |
|---|---|---|
| `docs/sysrs.md` | docs submodule | Move + add front matter |
| `docs/sysdes.md` | docs submodule | Move + add front matter |
| `docs/srs.md` | docs submodule | Move + add front matter |
| `docs/requirements/*` | docs submodule | Move (path records) |
| `docs/architecture/*` | docs submodule | Move + cleanup code paths |
| `docs/verification/*` | docs submodule | Move + add front matter |
| `docs/governance/*` | docs submodule | Move + add front matter |
| `docs/security/*` | docs submodule | Move + add front matter |
| `docs/privacy/*` | docs submodule | Move |
| `docs/legal/*` | docs submodule | Move |
| `docs/release/policy+go-nogo+waiver` | docs submodule | Move |
| `docs/references/*` | docs submodule | Move |
| `docs/ui-ux/*` | docs submodule | Move |
| `docs/i18n/*` | docs submodule | Move |
| `docs/material3-guideline.md` | docs submodule | Move |
| `docs/offline-knowledge/external/*` | docs submodule `references/` (flattened) | Move + rename |
| `docs/superpowers/*` | `dev-docs/superpowers/` | Move |
| `docs/offline-knowledge/` (remaining) | `dev-docs/offline-knowledge/` | Move |
| `docs/implementation-status-*` | `dev-docs/` | Move |
| `docs/release/ios-build.md` | `dev-docs/release/` | Move |
### 9.3 Cross-reference updates
All backtick path references (`docs/srs.md`) should become markdown links (`[SRS](../srs.md)` or `[SRS](srs.md)`) for both GitHub and MkDocs rendering.
### 9.4 Post-migration
- Remove `docs/` contents from code repo
- Add `chanora-docs` as git submodule at `docs/`
- Create `dev-docs/` directory with local-only files
- Update README references to new paths
- Write `AGENTS.md` with new conventions
- Update `opencode.json` or `.opencode/` references
## 10. AGENTS.md
An `AGENTS.md` file will be written at the code repo root documenting:
- The two-repo model (docs/ as submodule, dev-docs/ as local)
- What content goes where
- ASPICE traceability chain and rules
- Code architecture overview
- Verification commands
- Agent working conventions (no edits in docs/ without submodule awareness)
## 11. Deferred Items
| Item | Reason | When |
|---|---|---|
| Document provenance records | Convert completed ASPICE plans into provenance evidence | Follow-up task |
| Custom MkDocs traceability plugin | Core feature, built during implementation | Phase 1 |
| Cloudflare Pages + Access setup | Requires account creation, domain config | During deployment |
| `impl-mapping.md` creation | Extract code paths from SAD/SDD during migration | During migration |