Skip to content

Commit 528da16

Browse files
SchenLongclaude
andcommitted
chore(qa): post-deploy hygiene — gitignore, flaky test, hidden-feature doc
Close-out of 2026-04-15 MASTER-QA-PLAN handover after 2cccbbe deploy. - .gitignore: ignore deploy/.env, *.env.e2e.{prod,local}, /archive/, /coverage/, *.skill (skill packages belong in ~/.claude/skills/). - packages/dojolm-web/data/.gitignore: widen amaterasu-dna/ to full dir, add sengoku/campaigns/*.json with !index.json exception as named block. - Fix flaky auth/users/[id] route test: mock @/lib/auth/session so destroyUserSessions() no longer hits the real SQLite DB via a cwd-dependent path. Adds assertions that session revocation fires on disable and role-change (security-relevant side effect). 15/15 passes under both packages-scope and monorepo-root vitest contexts. - Commit HIDDEN-FEATURE-MAP-2026-04-13.md alongside existing sibling REMEDIATION-PLAN; fix absolute path in link. - Remove orphaned asset DojoLM- Text.jpg (no residual references). Verification: typecheck PASS, vitest 6252/6252 PASS. No production code change — Voyager deploy SHA remains 2cccbbe, no redeploy required. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2cccbbe commit 528da16

File tree

5 files changed

+277
-1
lines changed

5 files changed

+277
-1
lines changed

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,17 @@ presentation/
9292

9393
# Playwright MCP artifacts
9494
.playwright-mcp/
95+
96+
# Runtime / local environment files (never commit)
97+
deploy/.env
98+
*.env.e2e.prod
99+
*.env.e2e.local
100+
101+
# Historical backups (2GB+ of pre-QA snapshots; never commit). Root-anchored.
102+
/archive/
103+
104+
# Root-level coverage output (package-scoped entries above cover package dirs)
105+
/coverage/
106+
107+
# Claude Code skill packages belong in ~/.claude/skills/, not repo root
108+
*.skill

assets/DojoLM- Text.jpg

-1.51 MB
Binary file not shown.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Hidden Feature Map
2+
3+
Date: 2026-04-13
4+
Scope: `packages/dojolm-web`
5+
Audience: DojoLM web and platform team
6+
Follow-up plan: [HIDDEN-FEATURE-REMEDIATION-PLAN-2026-04-13.md](./HIDDEN-FEATURE-REMEDIATION-PLAN-2026-04-13.md)
7+
8+
## Executive Summary
9+
10+
This audit went deeper than the first-pass nav scan and found multiple distinct kinds of hidden capability:
11+
12+
- 4 hidden nav ids remain in the app's navigation model; 3 still render app surfaces and 1 (`armory`) appears to be legacy-only.
13+
- 22 of 30 dashboard widgets are hidden by default, and several of those widgets are the only practical entry points to otherwise buried workflows.
14+
- 33 Sensei tools expose platform actions that are richer than the explicit UI surface.
15+
- 1 secret keyboard sequence opens the module-visibility dialog.
16+
- Several advanced panels are data-gated or mock-backed, which makes them look partially implemented or randomly unavailable.
17+
18+
The highest-impact hidden-feature problem is not just "features without buttons." It is feature fragmentation:
19+
20+
- some capabilities are de-nav'd but still live,
21+
- some are only reachable through dashboard customization,
22+
- some only appear when prior state already exists,
23+
- some are assistant-only,
24+
- some are visible but non-live because they still use mock data.
25+
26+
## Hiddenness Taxonomy
27+
28+
This report uses five classes:
29+
30+
- `De-nav'd`: still routable and implemented, but removed from normal nav surfaces.
31+
- `Buried`: reachable through nested tabs, icon-only affordances, or secondary drawers.
32+
- `State-gated`: UI exists, but only appears after prior hidden state or cached data exists.
33+
- `Assistant-only`: primarily reachable through Sensei tool invocation rather than explicit UI.
34+
- `Mock-backed / non-live`: visible in UI but not wired to live backend behavior.
35+
36+
## Hidden Feature Map
37+
38+
| Surface | Class | Current access path | Why it is hidden | Evidence |
39+
| --- | --- | --- | --- | --- |
40+
| OBL behavioral analysis runs: alignment, robustness, geometry, depth | State-gated | No first-class trigger found in Jutsu, Scanner, or Atemi; context methods exist only in `BehavioralAnalysisContext` | Users can see OBL output only if cached data already exists; there is no obvious "run OBL" action | `src/lib/contexts/BehavioralAnalysisContext.tsx:128-205`, `src/components/llm/JutsuModelCard.tsx:37-39`, `src/components/scanner/ScannerInsightsPanel.tsx:158-180`, `src/components/adversarial/AdversarialLab.tsx:780-803` |
41+
| OBL visual panels in Atemi and Scanner | State-gated | Atemi and Scanner render them only when `oblResult` is already populated | The panels are mounted, but they are effectively invisible until some other missing action has already run analysis | `src/components/adversarial/AdversarialLab.tsx:780-803`, `src/components/scanner/ScannerInsightsPanel.tsx:158-204` |
42+
| `arena` module | De-nav'd | Deep link, widget routes, or assistant navigation | Still mounted in `page.tsx`, but hidden in `NAV_ITEMS` and filtered out of sidebar and command palette | `src/lib/constants.ts:110-117`, `src/app/page.tsx:247-259`, `src/components/layout/Sidebar.tsx:33-37`, `src/components/layout/CommandPalette.tsx:26-29` |
43+
| `sengoku` module | De-nav'd | Deep link, widget routes, or assistant navigation | Still mounted in `page.tsx`, but removed from primary nav and described as demoted into Atemi | `src/lib/constants.ts:127-136`, `src/app/page.tsx:196-204`, `src/components/layout/Sidebar.tsx:33-37` |
44+
| `strategic` legacy hub | De-nav'd | Deep link, some widget header clicks, mobile more drawer bug | Still a valid NavId, but route now renders only a retired notice; some dashboard targets still point to it | `src/lib/constants.ts:206-217`, `src/app/page.tsx:179-184`, `src/components/dashboard/NODADashboard.tsx:98-126` |
45+
| `armory` legacy nav id | Legacy-only | Assistant navigation or stale links | Still a NavId for back-compat and assistant tooling, but this audit did not find a live `page.tsx` renderer for it; content appears absorbed into Buki | `src/lib/constants.ts:82-88`, `src/components/buki/PayloadLab.tsx:1-8`, `src/lib/sensei/tool-definitions.ts:363-368`, `src/app/page.tsx:120-270` |
46+
| Dashboard personalization surface | Buried | Dashboard settings icon only | 22 of 30 widgets are hidden on first load, including multiple module entrypoints and status surfaces | `src/components/dashboard/NODADashboard.tsx:256-264`, `src/components/dashboard/DashboardCustomizer.tsx:239-266`, `src/components/dashboard/DashboardConfigContext.tsx:61-118` |
47+
| Hidden-by-default module entry widgets: Arena, Sengoku, Time Chamber, Kotoba, Ronin, SAGE, Mitsuke | Buried | Dashboard Customizer -> enable widget -> click widget | These widgets act like secondary navigation for hidden or low-salience modules, but users will not see them without customization | `src/components/dashboard/DashboardConfigContext.tsx:85-107`, `src/components/dashboard/widgets/SengokuWidget.tsx:25-35`, `src/components/dashboard/widgets/TimeChamberWidget.tsx:29-38`, `src/components/dashboard/widgets/KotobaWidget.tsx:24-33`, `src/components/dashboard/widgets/RoninHubWidget.tsx:93-103` |
48+
| Module Visibility dialog | Buried | Dashboard Customizer -> "Module Visibility" | Entire modules can be toggled on or off, but the control is hidden behind a second-order settings panel | `src/components/dashboard/DashboardCustomizer.tsx:259-266`, `src/components/dashboard/SenseiPanel.tsx:1-46` |
49+
| Secret keyboard sequence for module visibility | Buried | Dashboard only; hidden input sequence | A Konami-style key sequence opens the same dialog with no on-screen affordance | `src/hooks/useSenseiScroll.ts:1-64`, `src/components/dashboard/NODADashboard.tsx:335-346` |
50+
| Sensei assistant tool surface | Assistant-only | Top-bar bot button or floating Sensei button | Sensei can invoke 33 tools, including hidden navigation and workflows that do not have equivalent explicit CTAs | `src/components/layout/TopBar.tsx:117-126`, `src/components/sensei/SenseiDrawer.tsx:85-185`, `src/lib/sensei/tool-definitions.ts:765-811` |
51+
| Sensei navigation to hidden modules | Assistant-only | `navigate_to` accepts hidden module ids | Sensei can route users to `arena`, `strategic`, and `sengoku`, and it also still accepts the legacy-only `armory` id | `src/lib/sensei/tool-definitions.ts:361-390` |
52+
| Scanner Deep Scan / Shingan | Buried | Scanner -> `Deep Scan` tab | Powerful multi-format scanning is one level below Haiku Scanner and not promoted elsewhere | `src/app/page.tsx:286-313`, `src/app/page.tsx:385-389`, `src/components/shingan/ShinganPanel.tsx:423-829` |
53+
| Shingan batch and URL endpoints | UI/API gap | No first-class UI found for `/api/shingan/batch` or `/api/shingan/url` | Shingan UI does single scans and client-side batch looping over `/api/shingan/scan`, but dedicated batch and URL routes exist without a surfaced workflow | `src/components/shingan/ShinganPanel.tsx:518-583`, `src/app/api/shingan/batch/route.ts:1-75`, `src/app/api/shingan/url/route.ts:1-80` |
54+
| Atemi Playbooks sub-panels: Protocol Fuzz, Agentic, WebMCP | Buried | Atemi Lab -> Playbooks -> pill switcher | These are substantial sub-workspaces, but they are one level below Atemi and easy to miss | `src/components/adversarial/PlaybooksComposite.tsx:231-265` |
55+
| Protocol Fuzz | Mock-backed / non-live | Atemi Lab -> Playbooks -> Protocol Fuzz | Panel is reachable, but it runs mock async results rather than a real backend fuzz job | `src/components/scanner/ProtocolFuzzPanel.tsx:21-24`, `src/components/scanner/ProtocolFuzzPanel.tsx:107-115` |
56+
| WebMCP attack testing | Mock-backed / non-live | Atemi Lab -> Playbooks -> WebMCP | UI validates URL and categories but generates synthetic findings locally instead of calling a service | `src/components/adversarial/PlaybooksComposite.tsx:203-219` |
57+
| Payload Lab Generator (SAGE) | Buried | Buki -> `Generator` tab | SAGE was relocated out of Kumite and is now only discoverable inside Buki | `src/components/buki/PayloadLab.tsx:26-29`, `src/components/buki/PayloadLab.tsx:196-200` |
58+
| Payload Lab Fuzzer | Buried + non-live risk | Buki -> `Fuzzer` tab | Fuzzer UI exists but depends on `/api/buki/fuzz`, and no matching route was found under `src/app/api` | `src/components/buki/FuzzerPanel.tsx:10-12`, `src/components/buki/FuzzerPanel.tsx:50-74` |
59+
| Jutsu model detail workspace | Buried | Click a model card in Jutsu | Rich five-tab detail exists, but it is modal-only and lacks direct CTA for OBL or analytics actions | `src/components/llm/JutsuTab.tsx:327-349`, `src/components/llm/ModelDetailView.tsx:1-160` |
60+
| LLM leaderboard and analytics | Buried / relocated | Bushido Book -> `Results` tab | These surfaces were moved out of Model Lab, so users looking in Jutsu may not discover them | `src/components/llm/ModelLab.tsx:3-8`, `src/components/compliance/ComplianceCenter.tsx:450-458`, `src/components/compliance/ComplianceCenter.tsx:623-645`, `src/components/llm/AnalyticsWorkspace.tsx:22-120` |
61+
| Activity drawer as results surface | Buried | Top bar activity icon | ModelLab comments explicitly state results were demoted to activity surfaces rather than first-class model UI | `src/components/llm/ModelLab.tsx:5-7`, `src/components/layout/TopBar.tsx:114-165` |
62+
| Contrastive Prompt Bias tool | Orphaned | No live import found | Component exists but was not found mounted in the live tree during this audit | `src/components/adversarial/ContrastiveBiasCard.tsx:1-60` |
63+
64+
## Navigation and Discoverability Bugs
65+
66+
These are not just hidden features; they are concrete UI inconsistencies.
67+
68+
### 1. Mobile navigation leaks a hidden legacy item
69+
70+
The mobile `More` drawer filters `hidden` items for grouped modules, but not for ungrouped ones. That means the hidden `strategic` item can still appear on mobile.
71+
72+
Evidence:
73+
74+
- `src/lib/constants.ts:211-217`
75+
- `src/components/layout/MobileNav.tsx:209-279`
76+
77+
Impact:
78+
79+
- Desktop and command palette say `strategic` is retired.
80+
- Mobile can still offer it as a destination.
81+
82+
### 2. Dashboard widget header targets still point at retired or hidden modules
83+
84+
Several widget header clicks still navigate to `strategic`, which now renders only `KumiteRetiredNotice`.
85+
86+
Evidence:
87+
88+
- `src/components/dashboard/NODADashboard.tsx:98-126`
89+
- `src/app/page.tsx:179-184`
90+
91+
Affected widget targets:
92+
93+
- `threat-radar`
94+
- `arena-leaderboard`
95+
- `sage-status`
96+
- `mitsuke-alerts`
97+
98+
### 3. `arena-leaderboard` widget has conflicting destinations
99+
100+
The widget header target points to `strategic`, while the explicit action button routes to `adversarial`, not `arena`.
101+
102+
Evidence:
103+
104+
- `src/components/dashboard/NODADashboard.tsx:105-108`
105+
- `src/components/dashboard/widgets/ArenaLeaderboardWidget.tsx:40-49`
106+
107+
Impact:
108+
109+
- Users get three competing destinations for one Arena concept: retired Kumite, Atemi, and the hidden `arena` module.
110+
111+
### 4. Invalid widget target blocks Ecosystem Pulse navigation
112+
113+
`WIDGET_NAV_TARGET` uses `attackdna`, but the real nav id is `dna`. `WidgetCard` validates targets against `NAV_ITEMS`, so the header is silently non-clickable.
114+
115+
Evidence:
116+
117+
- `src/components/dashboard/NODADashboard.tsx:119-125`
118+
- `src/components/dashboard/WidgetCard.tsx:24-25`
119+
- `src/lib/constants.ts:175-181`
120+
121+
## Mock-Backed or Placeholder Surfaces
122+
123+
These features should either be promoted to live status or clearly labeled as preview/demo.
124+
125+
- `ProtocolFuzzPanel` uses mock results only.
126+
- WebMCP testing synthesizes findings locally.
127+
- `TransferMatrixPanel` uses a hardcoded matrix.
128+
- `ArenaLeaderboardWidget`, `SengokuWidget`, `TimeChamberWidget`, and `KotobaWidget` use mock widget data.
129+
- `orchestrator/status` returns a placeholder pending payload and is not wired into visible status UI.
130+
131+
Evidence:
132+
133+
- `src/components/scanner/ProtocolFuzzPanel.tsx:21-24`
134+
- `src/components/adversarial/PlaybooksComposite.tsx:213-219`
135+
- `src/components/llm/TransferMatrixPanel.tsx:19-30`
136+
- `src/components/dashboard/widgets/ArenaLeaderboardWidget.tsx:21-28`
137+
- `src/components/dashboard/widgets/SengokuWidget.tsx:14-20`
138+
- `src/components/dashboard/widgets/TimeChamberWidget.tsx:13-23`
139+
- `src/components/dashboard/widgets/KotobaWidget.tsx:13-18`
140+
- `src/app/api/orchestrator/status/route.ts:36-49`
141+
142+
## Assistant-Only Capability Inventory
143+
144+
Sensei is a real hidden feature surface, not just a helper chat box.
145+
146+
Notable tool-only or tool-first actions include:
147+
148+
- `create_arena_match`
149+
- `list_arena_matches`
150+
- `get_warriors`
151+
- `run_agentic_test`
152+
- `run_orchestrator`
153+
- `generate_attack`
154+
- `judge_response`
155+
- `list_campaigns`
156+
- `create_campaign`
157+
- `query_dna`
158+
- `analyze_dna`
159+
- `get_guard_audit`
160+
- `get_leaderboard`
161+
162+
Evidence:
163+
164+
- `src/lib/sensei/tool-definitions.ts:418-810`
165+
166+
Recommendation:
167+
168+
- Decide which of these are intentionally Sensei-only and label them as such.
169+
- For the rest, add "Open in UI" or "Create with Sensei" reciprocal entry points.
170+
171+
## API Surfaces With Weak or Missing UI Coverage
172+
173+
This is an appendix of likely UI-surface gaps, not a claim that every route is a bug. Dynamic fetch construction means simple grep undercounts some references.
174+
175+
High-signal candidates from this audit:
176+
177+
- `/api/shingan/batch`
178+
- `/api/shingan/url`
179+
- `/api/compliance/frameworks`
180+
- `/api/compliance/export`
181+
- `/api/orchestrator/status`
182+
- `/api/llm/coverage`
183+
- `/api/v1/arena`
184+
- `/api/v1/sengoku`
185+
- `/api/v1/timechamber`
186+
187+
Why they matter:
188+
189+
- they expose real or legacy feature intent,
190+
- they lack clear first-class UI entry points,
191+
- some are now out of sync with what the visible UI suggests.
192+
193+
Evidence:
194+
195+
- `src/app/api/shingan/batch/route.ts:1-75`
196+
- `src/app/api/shingan/url/route.ts:1-80`
197+
- `src/app/api/compliance/frameworks/route.ts:1-74`
198+
- `src/app/api/orchestrator/status/route.ts:1-65`
199+
- route-scan findings from this audit
200+
201+
## Recommended Surfacing Plan
202+
203+
### P0
204+
205+
- Add explicit `Run OBL` actions in Jutsu model cards or the model detail drawer.
206+
- Replace widget targets that still point to `strategic`.
207+
- Fix mobile `More` drawer so hidden ungrouped items are also filtered.
208+
- Decide whether `arena` and `sengoku` should remain hidden; if not, restore direct nav.
209+
210+
### P1
211+
212+
- Promote a small set of hidden-by-default widgets into role-based dashboard presets.
213+
- Add first-class CTAs from Jutsu to Bushido `Results` and from Bushido back to Jutsu models.
214+
- Add visible entry points for Shingan URL scan and true batch scan if those routes are meant to be used.
215+
- Add "Sensei can do more" affordances next to assistant-only workflows.
216+
217+
### P2
218+
219+
- Either wire live backends for Protocol Fuzz, WebMCP, Fuzzer, Transfer Matrix, and dashboard teaser widgets, or label them as preview/demo.
220+
- Remove or archive unmounted artifacts such as `ContrastiveBiasCard` if they are no longer part of the product plan.
221+
- Audit widget navigation targets for invalid or stale NavIds.
222+
223+
## Product-Level Takeaway
224+
225+
The codebase has more capability than the current UI communicates. The biggest issue is no longer raw implementation coverage; it is surfacing strategy and coherence.
226+
227+
Right now the platform has:
228+
229+
- hidden modules,
230+
- hidden widgets,
231+
- hidden assistant powers,
232+
- hidden state-dependent panels,
233+
- and hidden mock-vs-live status.
234+
235+
If the team wants users to "make the best of" what already exists, the next step is not only building new features. It is making the existing ones legible, reachable, and trustworthy.

packages/dojolm-web/data/.gitignore

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@ ecosystem/findings/
1212
validation/
1313
compliance-evidence/
1414
sengoku/runs/
15+
16+
# --- Sengoku campaigns: ignore runtime records, keep seed index ---
17+
# Must stay as a paired block — inserting any line between these two lines
18+
# will cause `index.json` to be re-ignored by the wildcard above it.
19+
sengoku/campaigns/*.json
20+
!sengoku/campaigns/index.json
21+
# --- end sengoku campaigns block ---
22+
1523
arena/matches/
1624
amaterasu-master/
17-
amaterasu-dna/ingest-meta.json
25+
# Full amaterasu-dna runtime tree (nodes/, ingest-meta.json, etc.)
26+
amaterasu-dna/
1827
admin-settings.json

packages/dojolm-web/src/app/api/auth/users/[id]/__tests__/route.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ vi.mock('@/lib/db/repositories/user.repository', () => ({
2929
},
3030
}));
3131

32+
// Route calls destroyUserSessions() on disable and role-update actions (route.ts:40,51).
33+
// Mock the session module so the test never touches the real SQLite DB. Without this,
34+
// the test becomes cwd-dependent: running under packages/dojolm-web/ finds migrations and
35+
// passes, but running from the monorepo root has no migrations dir, so the sessions table
36+
// is never created and DELETE throws "no such table" — swallowed by route.ts:56 → 500.
37+
// Local-var + forwarding-arrow style matches `login/__tests__/route.test.ts` so tests
38+
// can assert the session revocation fired on sensitive role/disable changes.
39+
const mockDestroyUserSessions = vi.fn();
40+
41+
vi.mock('@/lib/auth/session', () => ({
42+
destroyUserSessions: (...args: unknown[]) => mockDestroyUserSessions(...args),
43+
}));
44+
3245
vi.mock('@/lib/db/types', () => ({}));
3346

3447
// --- Helpers ---
@@ -82,6 +95,8 @@ describe('PATCH /api/auth/users/[id]', () => {
8295
const body = await res.json();
8396
expect(body.user).toEqual(disabledUser);
8497
expect(mockDisable).toHaveBeenCalledWith('user-1');
98+
// Disable must revoke the user's sessions (security-relevant side effect).
99+
expect(mockDestroyUserSessions).toHaveBeenCalledWith('user-1');
85100
});
86101

87102
// UID-003: Update role to admin
@@ -96,6 +111,8 @@ describe('PATCH /api/auth/users/[id]', () => {
96111
const body = await res.json();
97112
expect(body.user.role).toBe('admin');
98113
expect(mockUpdateRole).toHaveBeenCalledWith('user-1', 'admin');
114+
// Role change must revoke the user's sessions so old tokens don't retain the old role.
115+
expect(mockDestroyUserSessions).toHaveBeenCalledWith('user-1');
99116
});
100117

101118
// UID-004: Update role to viewer
@@ -110,6 +127,7 @@ describe('PATCH /api/auth/users/[id]', () => {
110127
const body = await res.json();
111128
expect(body.user.role).toBe('viewer');
112129
expect(mockUpdateRole).toHaveBeenCalledWith('user-1', 'viewer');
130+
expect(mockDestroyUserSessions).toHaveBeenCalledWith('user-1');
113131
});
114132

115133
// UID-005: Update role to analyst

0 commit comments

Comments
 (0)