instar 0.28.57 → 0.28.59
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/README.md +1 -0
- package/dashboard/index.html +226 -17
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +3 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/InitiativeTracker.d.ts +114 -0
- package/dist/core/InitiativeTracker.d.ts.map +1 -0
- package/dist/core/InitiativeTracker.js +232 -0
- package/dist/core/InitiativeTracker.js.map +1 -0
- package/dist/server/AgentServer.d.ts +2 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +119 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/0.28.58.md +54 -0
- package/upgrades/0.28.59.md +85 -0
- package/upgrades/side-effects/dashboard-resume-live-scroll.md +132 -0
- package/upgrades/side-effects/initiative-tracker-core-and-api.md +75 -0
- package/upgrades/side-effects/initiative-tracker-dashboard-tab.md +94 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Side-Effects Review — Dashboard "Resume live output" reliability + always-visible while paused
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `dashboard-resume-live-scroll`
|
|
4
|
+
**Date:** `2026-04-18`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required — pure UI timing fix, no gate/sentinel/watchdog/session-lifecycle surface`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The dashboard's "Resume live output" button (and the two sibling resume paths that
|
|
11
|
+
fire on scroll-to-bottom and wheel-down) did not reliably snap the xterm viewport
|
|
12
|
+
to the bottom after being clicked. The user still landed mid-history and had to
|
|
13
|
+
scroll manually to see live output — defeating the purpose of the button.
|
|
14
|
+
|
|
15
|
+
Root cause: `term.write(data)` is asynchronous — xterm parses and renders the
|
|
16
|
+
write on a microtask. The old code called `term.scrollToBottom()` either
|
|
17
|
+
immediately after the write or inside a `requestAnimationFrame`. Neither
|
|
18
|
+
guarantees the buffer has been populated, so `scrollToBottom()` snapped against
|
|
19
|
+
a stale `baseY` and landed above the new output.
|
|
20
|
+
|
|
21
|
+
Fix: use xterm's write-completion callback — `term.write(data, () => { term.scrollToBottom(); })`.
|
|
22
|
+
The callback fires after the write is flushed, so the scroll operates on the
|
|
23
|
+
final buffer state.
|
|
24
|
+
|
|
25
|
+
## Secondary change — button visibility mirrors follow state
|
|
26
|
+
|
|
27
|
+
The prior behavior only showed the "▼ Resume live output" button when new
|
|
28
|
+
output happened to arrive while the user was scrolled up — via `showResumeButton()`
|
|
29
|
+
inside `renderTerminalOutput`'s not-following branch. If the user scrolled up in
|
|
30
|
+
an idle session (no new output arriving), the button never appeared, and there
|
|
31
|
+
was no UI control to jump back to live.
|
|
32
|
+
|
|
33
|
+
Fix: `term.onScroll` now surfaces the button the moment the user scrolls up
|
|
34
|
+
(`!atBottom && userIsFollowing` → flip false + `showResumeButton()`), and hides
|
|
35
|
+
it the moment they return to the bottom by any means (`atBottom && !userIsFollowing`
|
|
36
|
+
→ flip true + `hideResumeButton()`). The wheel-down resume path hides the button
|
|
37
|
+
for the same reason. Session-switch reset calls `hideResumeButton()` so a
|
|
38
|
+
carry-over button from a prior session doesn't mislead.
|
|
39
|
+
|
|
40
|
+
The button is now the always-available "take me back to live" control, not a
|
|
41
|
+
contingent reaction to incoming output.
|
|
42
|
+
|
|
43
|
+
Files touched:
|
|
44
|
+
|
|
45
|
+
- `dashboard/index.html` — two overlapping changes:
|
|
46
|
+
- Three resume paths use xterm's write-completion callback:
|
|
47
|
+
1. The button `onclick` handler.
|
|
48
|
+
2. The `term.onScroll` handler's auto-resume branch.
|
|
49
|
+
3. The `xtermViewport` wheel handler's auto-resume branch.
|
|
50
|
+
The button handler retains `container.scrollIntoView({block:'end'})` in a
|
|
51
|
+
shared `snapToBottom` closure so both the pending-data and no-pending-data
|
|
52
|
+
branches do the same thing.
|
|
53
|
+
- Button visibility now mirrors `!userIsFollowing` from every path that
|
|
54
|
+
flips the flag: scroll-up in `onScroll`, scroll-to-bottom in `onScroll`,
|
|
55
|
+
wheel-down resume, and session-switch reset.
|
|
56
|
+
- `tests/unit/dashboard-resumeLive.test.ts` — new. HTML-inspection regression
|
|
57
|
+
test, consistent with the existing `dashboard-*.test.ts` pattern. Ten tests,
|
|
58
|
+
split across two describe blocks: (a) scroll-after-write invariants — locks
|
|
59
|
+
in the callback form on all three paths and bans the old
|
|
60
|
+
`term.write(data); requestAnimationFrame(() => term.scrollToBottom())` shape;
|
|
61
|
+
(b) button-visibility invariants — asserts show on scroll-up, hide on
|
|
62
|
+
scroll-to-bottom / wheel-resume / session switch.
|
|
63
|
+
|
|
64
|
+
## 1. Over-block
|
|
65
|
+
|
|
66
|
+
N/A — no block/allow decision exists in this change. The only behavior altered
|
|
67
|
+
is the timing of a DOM scroll call relative to an xterm write.
|
|
68
|
+
|
|
69
|
+
## 2. Under-block
|
|
70
|
+
|
|
71
|
+
N/A — see Over-block.
|
|
72
|
+
|
|
73
|
+
## 3. Level-of-abstraction fit
|
|
74
|
+
|
|
75
|
+
The fix lives at the exact layer where the bug lives: dashboard JavaScript
|
|
76
|
+
interacting with xterm.js. xterm's documented API for scheduling work after
|
|
77
|
+
a write is the write callback; using `requestAnimationFrame` was a workaround
|
|
78
|
+
that guessed at timing instead of using the library's native hook. The fix
|
|
79
|
+
moves us from guessing to the library-native pattern.
|
|
80
|
+
|
|
81
|
+
There is no higher-level authority that should own terminal-viewport scrolling
|
|
82
|
+
— this is a leaf UI behavior. There is no lower layer to delegate to — xterm
|
|
83
|
+
itself is the layer.
|
|
84
|
+
|
|
85
|
+
## 4. Signal-vs-authority compliance
|
|
86
|
+
|
|
87
|
+
No decision point touched. This is not a gate, filter, sentinel, watchdog, or
|
|
88
|
+
dispatcher. No brittle logic holds blocking authority. The principle does not
|
|
89
|
+
apply. Documented in Phase 1.
|
|
90
|
+
|
|
91
|
+
## 5. Interactions
|
|
92
|
+
|
|
93
|
+
- **`renderTerminalOutput` (the steady-state write path):** Still uses the
|
|
94
|
+
bare `term.write(data); term.scrollToBottom();` form. That path runs only
|
|
95
|
+
when `userIsFollowing === true` — xterm's default behavior is to auto-track
|
|
96
|
+
the bottom when the viewport is already there, so the follow-up
|
|
97
|
+
`scrollToBottom()` is essentially redundant and harmless. Not touched.
|
|
98
|
+
- **`term.onScroll` flag-flipping:** The scroll event fires during
|
|
99
|
+
`term.write()` as the buffer grows. If the write callback runs after
|
|
100
|
+
`scrollToBottom()`, the onScroll handler sees `atBottom === true` and
|
|
101
|
+
keeps `userIsFollowing === true`. No race with flag-flipping introduced.
|
|
102
|
+
- **Concurrent WS output:** If a new frame arrives via WebSocket during the
|
|
103
|
+
click handler, it calls `renderTerminalOutput` with `userIsFollowing === true`
|
|
104
|
+
(we just set it). That path does its own `term.clear()` + `term.write` +
|
|
105
|
+
`scrollToBottom()`, which will overwrite whatever our click handler was
|
|
106
|
+
mid-flight. Net effect: user ends up at bottom either way. No deadlock, no
|
|
107
|
+
torn state.
|
|
108
|
+
- **Infinite-scroll history loader:** Unaffected — uses `buf.viewportY <= 10`,
|
|
109
|
+
which is a separate concern from the bottom-snap logic.
|
|
110
|
+
|
|
111
|
+
## 6. External surfaces
|
|
112
|
+
|
|
113
|
+
None. This is client-side JavaScript in the dashboard HTML, served to the
|
|
114
|
+
user's browser. No other agents, no other users, no other systems observe the
|
|
115
|
+
change. No server API touched. No persisted state touched.
|
|
116
|
+
|
|
117
|
+
## 7. Rollback cost
|
|
118
|
+
|
|
119
|
+
Trivial: `git revert <commit>`. No data migration, no state repair, no release
|
|
120
|
+
coordination. Dashboard is static HTML served by the instar server; a revert
|
|
121
|
+
commit + server restart puts the old code back in front of the user
|
|
122
|
+
immediately.
|
|
123
|
+
|
|
124
|
+
## Verification
|
|
125
|
+
|
|
126
|
+
- HTML-inspection regression test passing (`tests/unit/dashboard-resumeLive.test.ts`, 6 tests).
|
|
127
|
+
- Full dashboard test suite passing (`dashboard-*.test.ts`, 20 tests total).
|
|
128
|
+
- Manual browser verification: deferred to user acceptance — reproducing the
|
|
129
|
+
bug end-to-end requires a live session with pending output and a user
|
|
130
|
+
scrolled up, which is Justin's original reporting path. The mechanical fix
|
|
131
|
+
is narrow enough (swap `term.write(data); scroll` → `term.write(data, scroll)`)
|
|
132
|
+
that the HTML regression is the load-bearing guarantee.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Side-Effects Review — Initiative Tracker core + API
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `initiative-tracker-core-and-api`
|
|
4
|
+
**Date:** `2026-04-17`
|
|
5
|
+
**Author:** `Echo (instar-developing agent)`
|
|
6
|
+
**Second-pass reviewer:** `not required — new opt-in persistence surface behind its own routes; no existing behavior is modified`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Introduces a long-running-work tracker to close the user-feedback gap where multi-phase efforts (e.g. the PR-REVIEW-HARDENING rollout) stall silently because there is no structural place to persist "this is phase N of M, last touched on D, needs user decision X." Existing primitives (AttentionItem = single actionable; Job = recurring cron; Dispatch = broadcast) don't cover this shape.
|
|
11
|
+
|
|
12
|
+
Files added/modified:
|
|
13
|
+
- **new** `src/core/InitiativeTracker.ts` — class with CRUD + phase transitions + digest scan; atomic-write persistence to `.instar/initiatives.json` following the existing AttentionItem pattern.
|
|
14
|
+
- **new** `tests/unit/InitiativeTracker.test.ts` — 28 unit tests (CRUD, validation, phase transitions, persistence, digest scan).
|
|
15
|
+
- **new** `tests/unit/routes-initiatives.test.ts` — 15 integration tests hitting real Express with real persistence.
|
|
16
|
+
- **mod** `src/server/AgentServer.ts` — adds `initiativeTracker` to constructor options and routeContext.
|
|
17
|
+
- **mod** `src/server/routes.ts` — RouteContext field + 7 new endpoints: `GET/POST /initiatives`, `GET /initiatives/digest`, `GET/PATCH/DELETE /initiatives/:id`, `POST /initiatives/:id/phase/:phaseId`.
|
|
18
|
+
- **mod** `src/commands/server.ts` — instantiates `InitiativeTracker` and passes to `new AgentServer`.
|
|
19
|
+
|
|
20
|
+
## Decision-point inventory
|
|
21
|
+
|
|
22
|
+
1. **Persistence location**: `.instar/initiatives.json` at the root of stateDir. Follows the same convention as `jobs.json`, `telegram-messages.jsonl`, etc. Alternative considered: nest under `.instar/state/initiatives/`. Rejected because the existing convention for top-level agent state is flat JSON at stateDir root.
|
|
23
|
+
2. **Persistence format**: single JSON file with `{ initiatives: [...] }` root (same shape as AttentionItem persistence). Not a directory-per-initiative. Rationale: expected volume is ≤50 records, fits comfortably in one atomic write; per-record writes would risk torn state.
|
|
24
|
+
3. **Ready-to-advance signal definition**: emits when the previous phase is `done` AND the current phase is `pending` (untouched). Does not emit once the current phase is `in-progress` — by then the advance has started and the signal would become noise. (Bug discovered mid-test-run and corrected.)
|
|
25
|
+
4. **Staleness threshold**: 7 days (`STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000`). Only applies to `active` initiatives. Configurable in a future change if noise is observed.
|
|
26
|
+
5. **Id validation**: lowercase kebab-case, 1–63 chars, must start with alphanumeric. Matches slug conventions used elsewhere (job slugs, topic names).
|
|
27
|
+
|
|
28
|
+
## 1. Over-block review
|
|
29
|
+
|
|
30
|
+
No blocking behavior introduced. Every endpoint is opt-in (503 when `initiativeTracker` is null, which cannot happen in practice since the server always instantiates it). No existing routes or middleware are changed. No pre-commit hook, git-sync behavior, or session lifecycle is touched.
|
|
31
|
+
|
|
32
|
+
## 2. Under-block review
|
|
33
|
+
|
|
34
|
+
N/A — there is no block-or-pass decision. The tracker is purely a persistence + query surface.
|
|
35
|
+
|
|
36
|
+
## 3. Level-of-abstraction fit
|
|
37
|
+
|
|
38
|
+
- Persistence logic lives in `InitiativeTracker` class (domain layer).
|
|
39
|
+
- HTTP shape lives in `routes.ts` (transport layer).
|
|
40
|
+
- Construction lives in `commands/server.ts` (composition root).
|
|
41
|
+
- Tests hit both the class layer and the HTTP layer independently.
|
|
42
|
+
|
|
43
|
+
This matches the pattern established by `BackupManager`, `RelationshipManager`, `FeedbackManager`.
|
|
44
|
+
|
|
45
|
+
## 4. Signal-vs-authority review
|
|
46
|
+
|
|
47
|
+
The digest scan emits **signals only** (`DigestItem[]`). It has zero blocking authority — callers (daily digest job, dashboard UI, the user) decide what to do with the signal. The daily digest job (separate commit) will use these signals to decide whether to push a Telegram notification; the threshold for pushing is the job's concern, not the tracker's.
|
|
48
|
+
|
|
49
|
+
## 5. Interactions review
|
|
50
|
+
|
|
51
|
+
- **Does not interact with Telegram**: tracker has no knowledge of topics, messages, or channels. The separate daily-digest job will bridge signal → Telegram.
|
|
52
|
+
- **Does not interact with JobScheduler**: the digest job reads the tracker but lives separately.
|
|
53
|
+
- **Does not interact with BackupManager**: `.instar/initiatives.json` is picked up by default backup globbing (no explicit include needed).
|
|
54
|
+
- **Does not conflict with AttentionItem**: complementary — AttentionItem is for single-shot actionables, Initiative is for multi-phase long-lived work. A future convenience could auto-create an AttentionItem from a `needs-user` digest row, but that's a separate design decision.
|
|
55
|
+
|
|
56
|
+
## 6. External surfaces
|
|
57
|
+
|
|
58
|
+
- **HTTP API**: 7 new routes under `/initiatives`, all behind the existing auth middleware. No public unauthenticated surface.
|
|
59
|
+
- **File system**: one new file (`.instar/initiatives.json`), atomic-rename writes prevent torn state.
|
|
60
|
+
- **Dashboard**: not wired yet — separate commit adds the Initiatives tab.
|
|
61
|
+
|
|
62
|
+
## 7. Rollback cost
|
|
63
|
+
|
|
64
|
+
Low. `git revert` removes the tracker class, routes, wiring, and tests. `.instar/initiatives.json` persists on disk but is ignored by all revived code paths (no other consumer reads it). The user can delete the file manually if desired, or leave it as dead data.
|
|
65
|
+
|
|
66
|
+
## Conclusion
|
|
67
|
+
|
|
68
|
+
Additive-only change. No existing behavior modified. 43 new tests (28 class + 15 route), all passing. `tsc --noEmit` clean. Clear to ship as commit 1 of 4 for the Initiative Tracker feature.
|
|
69
|
+
|
|
70
|
+
## Evidence pointers
|
|
71
|
+
|
|
72
|
+
- Unit tests: `tests/unit/InitiativeTracker.test.ts` (28 pass).
|
|
73
|
+
- Integration tests: `tests/unit/routes-initiatives.test.ts` (15 pass).
|
|
74
|
+
- Prior Phase A tests still green (spot-checked: `routes-prGatePhaseGate`, `PostUpdateMigrator-gitignore`, `PostUpdateMigrator-prPipelineArtifacts` — 37 pass).
|
|
75
|
+
- `npx tsc --noEmit` — clean.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Side-Effects Review — Initiative Tracker dashboard tab
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `initiative-tracker-dashboard-tab`
|
|
4
|
+
**Date:** `2026-04-17`
|
|
5
|
+
**Author:** `Echo (instar-developing agent)`
|
|
6
|
+
**Second-pass reviewer:** `not required — read-only UI surface, identical XSS-safety pattern to the PR Pipeline tab shipped in Phase A commit fb16f5c`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds an "Initiatives" tab to the dashboard that renders the initiatives
|
|
11
|
+
board at a glance: each initiative's title, status, phases (as pills),
|
|
12
|
+
last-touched date, blockers, and a digest summary of actionable signals
|
|
13
|
+
(stale / needs-user / next-check-due / ready-to-advance).
|
|
14
|
+
|
|
15
|
+
Read-only. No mutation buttons in this commit — the tracker already
|
|
16
|
+
exposes CRUD via the HTTP API, but the dashboard does not wire any of
|
|
17
|
+
those mutating endpoints to UI actions at this layer. Keeps the XSS
|
|
18
|
+
attack surface minimal while the tracker is new.
|
|
19
|
+
|
|
20
|
+
Files modified:
|
|
21
|
+
- `dashboard/index.html` — tab button, panel container, TAB_REGISTRY
|
|
22
|
+
entry, `loadInitiatives()` function (~170 lines).
|
|
23
|
+
- **new** `tests/unit/dashboard-initiativesTab.test.ts` — 9 smoke tests
|
|
24
|
+
inspecting the HTML to verify wiring, XSS invariant, and signal
|
|
25
|
+
rendering.
|
|
26
|
+
|
|
27
|
+
## Decision-point inventory
|
|
28
|
+
|
|
29
|
+
1. **Read-only vs. mutable UI**: read-only. The API supports mutation
|
|
30
|
+
via CRUD + phase-transition, but exposing those as buttons would
|
|
31
|
+
widen the XSS surface and introduce state-mutation from the browser.
|
|
32
|
+
Defer to a later commit after live-use feedback tells us what, if
|
|
33
|
+
anything, deserves a UI button.
|
|
34
|
+
2. **Digest panel placement**: top of tab content, only visible when
|
|
35
|
+
`digestRes.items.length > 0`. Zero items = quiet panel. Matches the
|
|
36
|
+
"don't push noise" principle for the digest job.
|
|
37
|
+
3. **Status filter default**: 'active'. Most-useful-first; user can
|
|
38
|
+
flip to 'All' / 'Completed' / 'Archived' / 'Abandoned' via the
|
|
39
|
+
dropdown.
|
|
40
|
+
4. **Count badge**: tab button shows the count of initiatives matching
|
|
41
|
+
the current filter (matches Sessions / Jobs tab convention).
|
|
42
|
+
|
|
43
|
+
## 1. Over-block / 2. Under-block review
|
|
44
|
+
|
|
45
|
+
N/A — tab is read-only. No gating decision in this surface.
|
|
46
|
+
|
|
47
|
+
## 3. Level-of-abstraction fit
|
|
48
|
+
|
|
49
|
+
UI rendering lives next to the PR Pipeline tab (same file, same
|
|
50
|
+
patterns, same XSS invariants). `loadInitiatives()` is a sibling of
|
|
51
|
+
`loadPrPipeline()` in layout and structure.
|
|
52
|
+
|
|
53
|
+
## 4. Signal-vs-authority review
|
|
54
|
+
|
|
55
|
+
The digest panel renders **signals** produced by the tracker; the UI
|
|
56
|
+
has no blocking authority over anything. A human user is a smart gate;
|
|
57
|
+
showing them signals is exactly the right hand-off.
|
|
58
|
+
|
|
59
|
+
## 5. Interactions review
|
|
60
|
+
|
|
61
|
+
- **No effect on PR Pipeline tab**: separate panel, separate loader,
|
|
62
|
+
separate TAB_REGISTRY entry.
|
|
63
|
+
- **No effect on other tabs**: tab registry is additive.
|
|
64
|
+
- **XSS**: all content goes through `textContent` or DOM element
|
|
65
|
+
construction; `innerHTML` is not used anywhere inside
|
|
66
|
+
`loadInitiatives()`. Enforced by a test.
|
|
67
|
+
|
|
68
|
+
## 6. External surfaces
|
|
69
|
+
|
|
70
|
+
- Dashboard HTML served to authenticated users (dashboard PIN or auth
|
|
71
|
+
token). No new public surface.
|
|
72
|
+
- Two new GET requests when the tab activates:
|
|
73
|
+
`GET /initiatives?status=active`, `GET /initiatives/digest`. Both
|
|
74
|
+
behind existing auth middleware.
|
|
75
|
+
|
|
76
|
+
## 7. Rollback cost
|
|
77
|
+
|
|
78
|
+
Very low. `git revert` removes the tab button, panel, registry entry,
|
|
79
|
+
and loader function — no residual state.
|
|
80
|
+
|
|
81
|
+
## Conclusion
|
|
82
|
+
|
|
83
|
+
Additive UI surface. 9 new smoke tests (all passing). `tsc --noEmit`
|
|
84
|
+
not affected (HTML/JS, not TS). XSS invariant guarded by a test that
|
|
85
|
+
greps the function body for `.innerHTML =` assignments.
|
|
86
|
+
|
|
87
|
+
Commit 2 of 4 for the Initiative Tracker feature (commit 1 = core +
|
|
88
|
+
API; commit 3 = daily digest job; commit 4 = seed real initiatives).
|
|
89
|
+
|
|
90
|
+
## Evidence pointers
|
|
91
|
+
|
|
92
|
+
- Smoke tests: `tests/unit/dashboard-initiativesTab.test.ts` (9 pass).
|
|
93
|
+
- Referenced spec: `docs/specs/INITIATIVE-TRACKER-SPEC.md`.
|
|
94
|
+
- Prior art: `dashboard/index.html` commit `fb16f5c` (PR Pipeline tab).
|