@rubytech/create-maxy-code 0.1.478 → 0.1.479
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +22 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
- package/payload/platform/lib/graph-style/src/index.ts +20 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +13 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
- package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
- package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
- package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
- package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +122 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/server/public/activity.html +8 -6
- package/payload/server/public/assets/{AdminLoginScreens-DR5eLF-a.js → AdminLoginScreens-DkxQZnvB.js} +1 -1
- package/payload/server/public/assets/AdminShell-Cv_nIFGe.js +2 -0
- package/payload/server/public/assets/Checkbox-CsTWK8mm.js +1 -0
- package/payload/server/public/assets/activity-BbnTiVBT.js +1 -0
- package/payload/server/public/assets/{admin-D2KLZnQW.js → admin-BZY3i_Ff.js} +1 -1
- package/payload/server/public/assets/{browser-CDWqEiCD.js → browser-BbIiSFKr.js} +1 -1
- package/payload/server/public/assets/{calendar-BG7fOpbB.js → calendar-BvxeTHvQ.js} +1 -1
- package/payload/server/public/assets/chat-DUKxufRk.js +1 -0
- package/payload/server/public/assets/chevron-left-BtfJD6LL.js +1 -0
- package/payload/server/public/assets/data-hl1wa-Ax.js +1 -0
- package/payload/server/public/assets/{graph-DRloNic4.js → graph-CDSGRoeb.js} +1 -1
- package/payload/server/public/assets/graph-labels-C5qLwnWA.js +1 -0
- package/payload/server/public/assets/{maximize-2-CzDaBugs.js → maximize-2-CAzH8dED.js} +1 -1
- package/payload/server/public/assets/{operator-BZeeyait.js → operator-BK7r8DAB.js} +1 -1
- package/payload/server/public/assets/{page-ef-6rRPL.js → page-B5YngOrK.js} +1 -1
- package/payload/server/public/assets/{page-B0obcg3b.js → page-BsCS0Dmc.js} +1 -1
- package/payload/server/public/assets/{public-9D3MMOkC.js → public-C1Pz-IBs.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-DXEKUhXP.js → rotate-ccw-UDln4Tou.js} +1 -1
- package/payload/server/public/assets/{tasks-BB0L7e5a.js → tasks-B-cLsn3p.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-CsYAjplS.js → time-entry-format-DPOBIE18.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-0lSBIUg-.js → triangle-alert-Dz-CZKo5.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-jTBQ2h0V.js → useCopyFeedback-wtUHGohk.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-BooArySZ.js → useSelectionMode-sph7AQT5.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-BRIluIKf.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-PUT8q_sm.js → useVoiceRecorder-DN4jYQ0A.js} +1 -1
- package/payload/server/public/brand-defaults.css +1 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/privacy.html +1 -0
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +6 -5
- package/payload/server/public/vnc-popout.html +1 -0
- package/payload/server/server.js +741 -366
- package/payload/server/public/assets/AdminShell-D9zaUxPd.js +0 -2
- package/payload/server/public/assets/Checkbox-DcbbAIe5.js +0 -1
- package/payload/server/public/assets/activity-C9P5NjEZ.js +0 -1
- package/payload/server/public/assets/chat-RyH-WYQC.js +0 -1
- package/payload/server/public/assets/chevron-left-DmXCr6nB.js +0 -1
- package/payload/server/public/assets/data-BaY_LkLR.js +0 -1
- package/payload/server/public/assets/graph-labels-DD-GqS4v.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-BZnzc5C3.js → useSubAccountSwitcher-v8LyBzZN.js} +0 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# Task 1818 — Widen the loop gate to all of `app/**` — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** `scripts/check-loops-registered.mjs` scans the whole of `app/**` rather than only `app/lib/**`, so a standing server loop armed anywhere under `app/` fails the build instead of running invisibly.
|
|
6
|
+
|
|
7
|
+
**Architecture:** One line changes behaviour — `SCAN_ROOTS` becomes `['server', 'app']`. That widening immediately fails the real tree on fifteen pre-existing browser timers, so the fifteen `// loop-exempt:client` markers must land in the same commit or `npm run prebuild` is broken between commits. The gate's SCOPE header currently asserts the opposite boundary and moves with the behaviour.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node ESM script (no dependencies), vitest, TypeScript/TSX source under `platform/ui`.
|
|
10
|
+
|
|
11
|
+
**Design doc:** `platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md`
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- All paths below are relative to `platform/ui/` inside the `maxy-code/` tree.
|
|
16
|
+
- The marker convention and the closed category set are settled by Task 1814 and must not change. Valid categories are exactly `per-connection`, `demand-singleton`, `client`, `registry-primitive`.
|
|
17
|
+
- Marker form is `// loop-exempt:<category> <reason>` appended to the physical line containing `setInterval(`. For a multi-line arrow body the comment goes after the opening brace, following the established form at `server/routes/public-reader.ts:257`.
|
|
18
|
+
- A reason must contain at least three consecutive letters (`REASON = /[A-Za-z]{3}/`) and must state something true and specific about that timer. Repeated boilerplate across the fifteen sites is a plan failure.
|
|
19
|
+
- The gate parses nothing. Do not add comment-stripping, string-awareness, or any TypeScript parsing — that is what killed its first version.
|
|
20
|
+
- Do not add a `registerLoop` call. The census found no standing loop in the newly covered area; adding one would be inventing a requirement.
|
|
21
|
+
- Every commit carries `Session: aad2dcb4-bc6c-4502-9ef9-e75995fb974b` and `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`.
|
|
22
|
+
|
|
23
|
+
**Baseline, measured before any change:**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
[check-loops-registered] ok files=284 sites=10 marked=9 allowlisted=1 (per-connection=6 demand-singleton=1 client=1 registry-primitive=1)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Predicted after:** `files=393 sites=25 marked=24 allowlisted=1 (per-connection=6 demand-singleton=1 client=16 registry-primitive=1)`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## File Structure
|
|
34
|
+
|
|
35
|
+
| File | Responsibility | Change |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `scripts/check-loops-registered.mjs` | The gate. Walks scan roots, matches raw lines, validates markers. | Modify: `SCAN_ROOTS` constant + SCOPE header block |
|
|
38
|
+
| `scripts/__tests__/check-loops-registered.test.ts` | Committed behaviour suite for the gate. | Modify: one existing case; add two; raise real-tree floors |
|
|
39
|
+
| 11 source files under `app/` outside `app/lib/` | Browser components and hooks that arm timers. | Modify: append a marker comment to 15 lines |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### Task 1: Move the test surface to the new boundary
|
|
44
|
+
|
|
45
|
+
The existing suite contains a case that **encodes the hole this task closes**. It proves "files outside the two scan roots are ignored" using `app/chat/page.tsx` — a file that becomes in-scope. Left alone it would fail; edited carelessly it would silently stop testing anything. It must be re-pointed at a path that is genuinely outside both roots.
|
|
46
|
+
|
|
47
|
+
**Files:**
|
|
48
|
+
- Modify: `scripts/__tests__/check-loops-registered.test.ts:87-91` (the mis-pointed case)
|
|
49
|
+
- Modify: `scripts/__tests__/check-loops-registered.test.ts` (add two cases after line 91)
|
|
50
|
+
|
|
51
|
+
**Interfaces:**
|
|
52
|
+
- Consumes: `runGate(files: Record<string, string>): { code: number; out: string }` — the existing helper at line 25. It writes the given files into a temp dir and runs the gate with `--root <dir>`, returning exit code and combined stdout+stderr.
|
|
53
|
+
- Produces: nothing consumed by later tasks.
|
|
54
|
+
|
|
55
|
+
- [ ] **Step 1: Re-point the mis-pointed case and add the two new ones**
|
|
56
|
+
|
|
57
|
+
Replace lines 87–91 of `scripts/__tests__/check-loops-registered.test.ts`:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
it('ignores files outside the two scan roots', () => {
|
|
61
|
+
const r = runGate({ 'app/chat/page.tsx': 'setInterval(f, 1)\n' })
|
|
62
|
+
expect(r.code).toBe(0)
|
|
63
|
+
expect(r.out).toContain('sites=0')
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
with:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Task 1818 re-pointed this case. It previously proved the point with
|
|
71
|
+
// `app/chat/page.tsx`, which is now IN scope — the precise hole 1818 closes.
|
|
72
|
+
// `scripts/` is outside both roots and is where the gate itself lives, so it
|
|
73
|
+
// is a stable choice that will not quietly migrate in-scope later.
|
|
74
|
+
it('ignores files outside the scan roots', () => {
|
|
75
|
+
const r = runGate({ 'scripts/some-tool.mjs': 'setInterval(f, 1)\n' })
|
|
76
|
+
expect(r.code).toBe(0)
|
|
77
|
+
expect(r.out).toContain('sites=0')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Task 1818 — the gap 1814 left. A standing loop armed in a component or
|
|
81
|
+
// route file under `app/` but outside `app/lib/` was invisible to the gate.
|
|
82
|
+
it('fails on an unmarked setInterval under app/ outside app/lib', () => {
|
|
83
|
+
const r = runGate({ 'app/chat/page.tsx': 'setInterval(() => poll(), 1000)\n' })
|
|
84
|
+
expect(r.code).toBe(1)
|
|
85
|
+
expect(r.out).toContain('app/chat/page.tsx')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('passes a marked client site under app/ outside app/lib', () => {
|
|
89
|
+
const r = runGate({
|
|
90
|
+
'app/Sidebar.tsx':
|
|
91
|
+
'const t = setInterval(poll, 5000) // loop-exempt:client sidebar poll in the browser\n',
|
|
92
|
+
})
|
|
93
|
+
expect(r.code).toBe(0)
|
|
94
|
+
expect(r.out).toContain('sites=1')
|
|
95
|
+
expect(r.out).toContain('client=1')
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- [ ] **Step 2: Run the suite to verify the two new cases fail**
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cd maxy-code/platform/ui && npx vitest run scripts/__tests__/check-loops-registered.test.ts
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Expected: the re-pointed `ignores files outside the scan roots` PASSES (`scripts/` is outside both roots today and after). `fails on an unmarked setInterval under app/ outside app/lib` FAILS — the gate exits 0 because `app/chat/page.tsx` is not scanned. `passes a marked client site under app/ outside app/lib` FAILS on `expect(r.out).toContain('sites=1')`, receiving `sites=0` for the same reason.
|
|
106
|
+
|
|
107
|
+
Two failures is the RED state. If either new case passes here, the scan root was already wider than the baseline and the premise of this task needs re-checking before continuing.
|
|
108
|
+
|
|
109
|
+
- [ ] **Step 3: Commit the red tests**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git add maxy-code/platform/ui/scripts/__tests__/check-loops-registered.test.ts
|
|
113
|
+
git commit -m "test: assert the loop gate covers app/ outside app/lib (red)
|
|
114
|
+
|
|
115
|
+
Re-points the case that proved the hole using app/chat/page.tsx, which this
|
|
116
|
+
task brings in scope, and adds the two cases 1818 requires.
|
|
117
|
+
|
|
118
|
+
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
119
|
+
Session: aad2dcb4-bc6c-4502-9ef9-e75995fb974b"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Task 2: Widen the scan root and mark the fifteen browser timers
|
|
125
|
+
|
|
126
|
+
These land together. Widening alone fails the real tree on fifteen unmarked sites, which breaks `npm run prebuild` for every other sprint in the repo until the markers land.
|
|
127
|
+
|
|
128
|
+
**Files:**
|
|
129
|
+
- Modify: `scripts/check-loops-registered.mjs:53-60` (SCOPE header), `scripts/check-loops-registered.mjs:74-75` (`SCAN_ROOTS`)
|
|
130
|
+
- Modify: `app/AdminShell.tsx:228`, `app/Sidebar.tsx:512,534,1609`, `app/activity/page.tsx:428`, `app/chat/page.tsx:700,721`, `app/chat/transcript-render.tsx:80`, `app/graph/page.tsx:1456`, `app/graph/settle-lock.ts:137`, `app/hooks/useViewMode.ts:110`, `app/hooks/useVoiceRecorder.ts:142`, `app/useAdminAuth.ts:219`, `app/useElapsedTimer.ts:29,35`
|
|
131
|
+
|
|
132
|
+
**Interfaces:**
|
|
133
|
+
- Consumes: nothing from Task 1.
|
|
134
|
+
- Produces: the gate's counter line, whose exact predicted value Task 3 asserts.
|
|
135
|
+
|
|
136
|
+
- [ ] **Step 1: Widen `SCAN_ROOTS`**
|
|
137
|
+
|
|
138
|
+
Replace lines 74–75 of `scripts/check-loops-registered.mjs`:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
/** The two trees that contain server-side code. */
|
|
142
|
+
const SCAN_ROOTS = ['server', 'app/lib']
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
with:
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
/** `app/lib` is deliberately NOT listed alongside `app` — it is a subtree of
|
|
149
|
+
* it, and naming both would walk it twice, doubling its contribution to the
|
|
150
|
+
* counts printed below. Those counts are the evidence that the scan actually
|
|
151
|
+
* read the tree, so inflating them defeats their purpose. */
|
|
152
|
+
const SCAN_ROOTS = ['server', 'app']
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- [ ] **Step 2: Rewrite the SCOPE header block**
|
|
156
|
+
|
|
157
|
+
Replace lines 53–60 of `scripts/check-loops-registered.mjs`:
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// SCOPE — exact, because a gate believed to cover more than it does is worse
|
|
161
|
+
// than none.
|
|
162
|
+
//
|
|
163
|
+
// Covers `server/**` and `app/lib/**`, excluding `__tests__`. Every standing
|
|
164
|
+
// server loop in the tree lives in one of those two, including all four gateway
|
|
165
|
+
// sweeps. NOT covered: the rest of `app/**`, which is Next.js route and
|
|
166
|
+
// component code running in the visitor's browser, and test files, which are
|
|
167
|
+
// not production loops.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
with:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
// SCOPE — exact, because a gate believed to cover more than it does is worse
|
|
174
|
+
// than none.
|
|
175
|
+
//
|
|
176
|
+
// Covers `server/**` and `app/**`, excluding `__tests__`. Not covered: test
|
|
177
|
+
// files, which are not production loops, and everything outside those two
|
|
178
|
+
// trees (`scripts/`, `e2e/`, config), which does not run in the server process.
|
|
179
|
+
//
|
|
180
|
+
// Task 1818 widened the second root from `app/lib` to all of `app`. 1814 had
|
|
181
|
+
// scanned `app/lib` only, on a census showing every standing server loop lived
|
|
182
|
+
// there. That was true and is not the point: the gate's job is what could be
|
|
183
|
+
// added, not what is present. Two findings made the narrower root indefensible.
|
|
184
|
+
//
|
|
185
|
+
// 1. There is no route-file surface to enumerate instead. This package is a
|
|
186
|
+
// Vite SPA (ten HTML entries) plus a Hono server bundled from exactly two
|
|
187
|
+
// entries, `server/index.ts` and `server/edge.ts`. There is no Next.js and
|
|
188
|
+
// no `app/api/`, so "scan the server-executing subtrees of app/" has no
|
|
189
|
+
// subtree to name, and any list would be a list again — the property 1814
|
|
190
|
+
// removed.
|
|
191
|
+
// 2. Server-reachable code outside `app/lib/` already exists.
|
|
192
|
+
// `server/routes/whatsapp-reader.ts` imports SUPPORTED_MIME_SET,
|
|
193
|
+
// MAX_FILE_BYTES and MAX_ATTACHMENTS — runtime values, not types — from
|
|
194
|
+
// `app/admin-types.ts`. It arms no timer, so nothing was hidden, but the
|
|
195
|
+
// boundary "server code stops at app/lib" was already false.
|
|
196
|
+
//
|
|
197
|
+
// `'use client'` is not the discriminator and cannot become one: three of the
|
|
198
|
+
// eleven files under `app/` that arm a browser timer carry no such directive
|
|
199
|
+
// (`chat/page.tsx`, `chat/transcript-render.tsx`, `graph/settle-lock.ts`).
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- [ ] **Step 3: Mark all fifteen sites**
|
|
203
|
+
|
|
204
|
+
Append to the end of each listed line, exactly as given. Each reason states what that specific timer does; do not substitute a shared phrase.
|
|
205
|
+
|
|
206
|
+
`app/AdminShell.tsx:228`
|
|
207
|
+
```typescript
|
|
208
|
+
const id = setInterval(read, 60000) // loop-exempt:client polls /api/health for the Claude auth badge in the admin shell, browser-side
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`app/Sidebar.tsx:512`
|
|
212
|
+
```typescript
|
|
213
|
+
timer = setInterval(() => { void fetchChannelConvos() }, CHANNEL_CONVO_POLL_MS) // loop-exempt:client refreshes the sidebar's channel conversation list in the browser
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`app/Sidebar.tsx:534`
|
|
217
|
+
```typescript
|
|
218
|
+
timer = setInterval(tick, SESSIONS_POLL_MS) // loop-exempt:client refreshes the sidebar's session list in the browser
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`app/Sidebar.tsx:1609`
|
|
222
|
+
```typescript
|
|
223
|
+
timer = setInterval(() => { void poll() }, SYSTEM_STATS_POLL_MS) // loop-exempt:client refreshes the sidebar's system-stats readout in the browser
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`app/activity/page.tsx:428`
|
|
227
|
+
```typescript
|
|
228
|
+
const t = setInterval(() => void load(range), 15_000) // loop-exempt:client reloads the /activity table in the browser; the loops it displays are the registered ones
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`app/chat/page.tsx:700`
|
|
232
|
+
```typescript
|
|
233
|
+
const interval = setInterval(() => { void fetchPointer() }, POINTER_POLL_MS) // loop-exempt:client polls the transcript pointer for the open chat tab
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`app/chat/page.tsx:721`
|
|
237
|
+
```typescript
|
|
238
|
+
const id = setInterval(read, 60000) // loop-exempt:client polls /api/health for the Claude auth badge on the chat page, browser-side
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`app/chat/transcript-render.tsx:80`
|
|
242
|
+
```typescript
|
|
243
|
+
const id = setInterval(() => setNow(Date.now()), 1000) // loop-exempt:client one-second render clock advancing relative timestamps in the transcript
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`app/graph/page.tsx:1456`
|
|
247
|
+
```typescript
|
|
248
|
+
const intervalId = window.setInterval(poll, POLL_MS) // loop-exempt:client polls graph layout progress in the browser; window-scoped by construction
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`app/graph/settle-lock.ts:137`
|
|
252
|
+
```typescript
|
|
253
|
+
timer = setInterval(() => { // loop-exempt:client samples vis-network node positions to freeze canvas physics once the layout is still
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`app/hooks/useViewMode.ts:110`
|
|
257
|
+
```typescript
|
|
258
|
+
pollInterval.current = setInterval(() => { // loop-exempt:client watches whether the popout window was closed, so the parent tab can restore its own view
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`app/hooks/useVoiceRecorder.ts:142`
|
|
262
|
+
```typescript
|
|
263
|
+
timerRef.current = setInterval(() => { // loop-exempt:client advances the recording elapsed-time display while the mic is open
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`app/useAdminAuth.ts:219`
|
|
267
|
+
```typescript
|
|
268
|
+
const interval = setInterval(async () => { // loop-exempt:client polls /api/health to keep the browser's auth signal current while a chat is open
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`app/useElapsedTimer.ts:29`
|
|
272
|
+
```typescript
|
|
273
|
+
sessionTickRef.current = setInterval(() => { // loop-exempt:client advances the per-session turn clock rendered in the browser
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`app/useElapsedTimer.ts:35`
|
|
277
|
+
```typescript
|
|
278
|
+
elapsedRef.current = setInterval(() => { // loop-exempt:client advances the elapsed-seconds counter rendered in the browser
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
- [ ] **Step 4: Run the gate against the real tree**
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
cd maxy-code/platform/ui && node scripts/check-loops-registered.mjs
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Expected, exactly:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
[check-loops-registered] ok files=393 sites=25 marked=24 allowlisted=1 (per-connection=6 demand-singleton=1 client=16 registry-primitive=1)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
If it exits 1, a marker is missing or malformed — the error names the file and line. If it exits 0 but `files=` is not 393, the walk is not reading what this plan predicts; stop and re-census rather than adjusting the expected number to match.
|
|
294
|
+
|
|
295
|
+
- [ ] **Step 5: Run the gate's suite**
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
cd maxy-code/platform/ui && npx vitest run scripts/__tests__/check-loops-registered.test.ts
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Expected: all cases pass, including Task 1's two, which were red.
|
|
302
|
+
|
|
303
|
+
- [ ] **Step 6: Commit**
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
git add maxy-code/platform/ui/scripts/check-loops-registered.mjs \
|
|
307
|
+
maxy-code/platform/ui/app
|
|
308
|
+
git commit -m "feat: scan all of app/ in the loop gate, not just app/lib
|
|
309
|
+
|
|
310
|
+
Widens SCAN_ROOTS to ['server', 'app'] and marks the fifteen pre-existing
|
|
311
|
+
browser timers loop-exempt:client. Both land together: widening alone fails
|
|
312
|
+
prebuild on fifteen unmarked sites.
|
|
313
|
+
|
|
314
|
+
Header rewritten — it asserted the rest of app/ was Next.js route code, which
|
|
315
|
+
this package has none of, and that app/lib was where server code stopped,
|
|
316
|
+
which server/routes/whatsapp-reader.ts already disproves.
|
|
317
|
+
|
|
318
|
+
files=284 -> 393, sites=10 -> 25, client=1 -> 16.
|
|
319
|
+
|
|
320
|
+
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
321
|
+
Session: aad2dcb4-bc6c-4502-9ef9-e75995fb974b"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### Task 3: Prove the signal is live, then raise the real-tree floors
|
|
327
|
+
|
|
328
|
+
Task 1's new cases run under `--root` against a synthetic tree. They prove the gate's *logic* handles `app/`, but they cannot prove the *real* scan root moved — a gate still hardcoded to `app/lib` internally would pass them if the fixture happened to be reachable. The task requires the gate observed failing on a real violation in the newly covered area. That is what this task does.
|
|
329
|
+
|
|
330
|
+
**Files:**
|
|
331
|
+
- Modify: `scripts/__tests__/check-loops-registered.test.ts:110-116` (the real-tree floors)
|
|
332
|
+
- Temporarily modify then revert: `app/Sidebar.tsx`
|
|
333
|
+
|
|
334
|
+
**Interfaces:**
|
|
335
|
+
- Consumes: the counter line produced by Task 2.
|
|
336
|
+
- Produces: nothing.
|
|
337
|
+
|
|
338
|
+
- [ ] **Step 1: Mutation check — insert a real violation**
|
|
339
|
+
|
|
340
|
+
Add this line at module scope in `app/Sidebar.tsx`, immediately after the final `import` statement. Module scope, not inside a component, so it is a genuine standing-loop shape rather than a mount-scoped one:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
setInterval(() => { void fetch('/api/health') }, 30000)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- [ ] **Step 2: Observe the gate fail on it**
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
cd maxy-code/platform/ui && node scripts/check-loops-registered.mjs; echo "exit=$?"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Expected: `exit=1`, with output naming `app/Sidebar.tsx`, the line number of the inserted statement, and `no loop-exempt marker`.
|
|
353
|
+
|
|
354
|
+
**Record the exact output in the debrief.** A pass here is the whole point of the task — if the gate exits 0, the scan root did not actually move and Task 2 is not done, regardless of what the synthetic tests say.
|
|
355
|
+
|
|
356
|
+
- [ ] **Step 3: Revert the mutation**
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
cd maxy-code/platform/ui && git checkout app/Sidebar.tsx
|
|
360
|
+
node scripts/check-loops-registered.mjs; echo "exit=$?"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Expected: `exit=0` and the counter line back to `files=393 sites=25 marked=24 allowlisted=1`.
|
|
364
|
+
|
|
365
|
+
Confirm the revert is complete before continuing:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
git diff --stat -- app/Sidebar.tsx
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Expected: no output. If `Sidebar.tsx` still differs, the marker from Task 2 was reverted along with the mutation — re-apply it.
|
|
372
|
+
|
|
373
|
+
- [ ] **Step 4: Raise the real-tree floors**
|
|
374
|
+
|
|
375
|
+
Replace lines 110–116 of `scripts/__tests__/check-loops-registered.test.ts`:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
it('reports non-zero counts against the real tree, so a dead scanner is visible', () => {
|
|
379
|
+
const out = execFileSync('node', [GATE], { encoding: 'utf8' })
|
|
380
|
+
const files = Number(/files=(\d+)/.exec(out)?.[1] ?? 0)
|
|
381
|
+
const sites = Number(/sites=(\d+)/.exec(out)?.[1] ?? 0)
|
|
382
|
+
expect(files).toBeGreaterThanOrEqual(20)
|
|
383
|
+
expect(sites).toBeGreaterThanOrEqual(9)
|
|
384
|
+
})
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
with:
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
it('reports non-zero counts against the real tree, so a dead scanner is visible', () => {
|
|
391
|
+
const out = execFileSync('node', [GATE], { encoding: 'utf8' })
|
|
392
|
+
const files = Number(/files=(\d+)/.exec(out)?.[1] ?? 0)
|
|
393
|
+
const sites = Number(/sites=(\d+)/.exec(out)?.[1] ?? 0)
|
|
394
|
+
expect(files).toBeGreaterThanOrEqual(20)
|
|
395
|
+
expect(sites).toBeGreaterThanOrEqual(9)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// Task 1818 — the floors above predate the widening and would still pass with
|
|
399
|
+
// `app/` unscanned, so they cannot detect this task being reverted. These are
|
|
400
|
+
// keyed to the widened tree instead. They are floors, not equalities: source
|
|
401
|
+
// files get added routinely and an equality would fail on unrelated work.
|
|
402
|
+
// A revert to the `app/lib`-only root collapses files to ~284 and client to
|
|
403
|
+
// 1, so both of these fail, which is what they are for.
|
|
404
|
+
it('scans the whole of app/, not just app/lib', () => {
|
|
405
|
+
const out = execFileSync('node', [GATE], { encoding: 'utf8' })
|
|
406
|
+
const files = Number(/files=(\d+)/.exec(out)?.[1] ?? 0)
|
|
407
|
+
const client = Number(/client=(\d+)/.exec(out)?.[1] ?? 0)
|
|
408
|
+
expect(files).toBeGreaterThanOrEqual(380)
|
|
409
|
+
expect(client).toBeGreaterThanOrEqual(16)
|
|
410
|
+
})
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
- [ ] **Step 5: Run the full gate suite**
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
cd maxy-code/platform/ui && npx vitest run scripts/__tests__/check-loops-registered.test.ts
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Expected: all cases pass, including the new floor case.
|
|
420
|
+
|
|
421
|
+
- [ ] **Step 6: Run the whole prebuild gate chain**
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
cd maxy-code/platform/ui && npm run prebuild
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Expected: every gate exits 0. `check-loops-registered` prints the Task 2 counter line. This confirms the widened gate has not broken any sibling gate and that the real build path is clean.
|
|
428
|
+
|
|
429
|
+
- [ ] **Step 7: Commit**
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
git add maxy-code/platform/ui/scripts/__tests__/check-loops-registered.test.ts
|
|
433
|
+
git commit -m "test: floor the loop gate's counts to the widened tree
|
|
434
|
+
|
|
435
|
+
The pre-existing floors (files>=20, sites>=9) pass with app/ unscanned, so
|
|
436
|
+
they cannot detect this task being reverted. Adds floors keyed to the widened
|
|
437
|
+
root: files>=380, client>=16.
|
|
438
|
+
|
|
439
|
+
Mutation-checked: a module-scope setInterval inserted into app/Sidebar.tsx is
|
|
440
|
+
observed failing the gate at exit 1, then reverted.
|
|
441
|
+
|
|
442
|
+
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
443
|
+
Session: aad2dcb4-bc6c-4502-9ef9-e75995fb974b"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Verification surface
|
|
449
|
+
|
|
450
|
+
Run from `maxy-code/platform/ui`.
|
|
451
|
+
|
|
452
|
+
| Check | Command | Expected |
|
|
453
|
+
|---|---|---|
|
|
454
|
+
| Gate, real tree | `node scripts/check-loops-registered.mjs` | `files=393 sites=25 marked=24 allowlisted=1 (… client=16 …)` |
|
|
455
|
+
| Gate suite | `npx vitest run scripts/__tests__/check-loops-registered.test.ts` | all pass |
|
|
456
|
+
| Full prebuild chain | `npm run prebuild` | all gates exit 0 |
|
|
457
|
+
| Mutation check | Task 3 Steps 1–3 | exit 1 naming `app/Sidebar.tsx`, then exit 0 after revert |
|
|
458
|
+
| No behaviour change in app | `git diff -- app/` | comment-only additions; no statement altered |
|
|
459
|
+
|
|
460
|
+
There is no browser verification step. This task changes a build gate and appends comments; it alters no rendered output and no runtime behaviour. `git diff -- app/` showing comment-only changes is the evidence for that claim.
|
|
461
|
+
|
|
462
|
+
**Known pre-existing red, not caused by this task:** two tests in `server/lib/top-level-labels.test.ts` and `server/routes/__tests__/graph-default-topLevel.test.ts` fail on `main` and in any worktree, tracked as Task 1819. Neither imports anything this task touches. Do not attempt to fix them here.
|