instar 1.3.1169 → 1.3.1170

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.
@@ -3634,6 +3634,11 @@
3634
3634
 
3635
3635
  <!-- subscriptions tab (Subscription & Auth Standard P2.2 — live quota + pending logins) -->
3636
3636
  <style>
3637
+ /* Provider group heading — only rendered when more than one provider is
3638
+ enrolled, so a single-provider install looks exactly as it did before. */
3639
+ .sub-provider-heading { font-size:12px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
3640
+ opacity:.6; margin:14px 0 8px; padding-bottom:6px; border-bottom:1px solid var(--border,#2a2a2a); }
3641
+ .sub-provider-heading:first-child { margin-top:0; }
3637
3642
  .sub-account { border:1px solid var(--border,#2a2a2a); border-radius:10px; padding:14px 16px; margin-bottom:12px; }
3638
3643
  .sub-account-inuse { border-color:#16a34a; box-shadow:0 0 0 1px #16a34a44; }
3639
3644
  .sub-account-inuse-badge { font-size:11px; font-weight:600; color:#22c55e; margin-right:auto; }
@@ -195,6 +195,47 @@ export function quotaBar(doc, label, pct, resetIso, now = Date.now()) {
195
195
  return wrap;
196
196
  }
197
197
 
198
+ /**
199
+ * Order accounts so each provider's accounts sit together, interleaving a heading
200
+ * marker before each group. Returns a flat list of accounts with `{__providerHeading}`
201
+ * markers spliced in, so the caller keeps ONE loop and one card-rendering path.
202
+ *
203
+ * Two deliberate choices:
204
+ *
205
+ * - **Groups appear in first-appearance order, and accounts keep their order within a
206
+ * group.** Nothing is sorted. The pool's order carries meaning the dashboard cannot
207
+ * see (enrolment order, which account is the default), so grouping re-associates
208
+ * without re-ranking.
209
+ *
210
+ * - **A single-provider list gets NO heading.** Every instar install with one provider
211
+ * renders exactly as it did before — the heading only appears once there is genuinely
212
+ * something to tell apart, which is the condition that prompted this. That property is
213
+ * pinned by a test, because "we grouped everything" would quietly add a redundant
214
+ * heading to the common case.
215
+ */
216
+ export function groupAccountsByProvider(accounts) {
217
+ if (!Array.isArray(accounts)) return [];
218
+ const groups = new Map(); // provider key (raw) → accounts, in first-appearance order
219
+ for (const a of accounts) {
220
+ const key = a && typeof a.provider === 'string' ? a.provider : '';
221
+ if (!groups.has(key)) groups.set(key, []);
222
+ groups.get(key).push(a);
223
+ }
224
+ const out = [];
225
+ const multi = groups.size > 1;
226
+ for (const [key, members] of groups) {
227
+ if (multi) {
228
+ // friendlyProvider maps anthropic→Claude / openai→Codex and SANITIZES anything
229
+ // else, so an unrecognised or hostile provider string cannot reach the DOM raw.
230
+ // An absent provider has no honest name, so say so rather than invent one.
231
+ const label = friendlyProvider(key) || 'Other';
232
+ out.push({ __providerHeading: label });
233
+ }
234
+ for (const m of members) out.push(m);
235
+ }
236
+ return out;
237
+ }
238
+
198
239
  /** Per-account rows: nickname, status, provider·framework, 5h + weekly quota bars.
199
240
  * `inUseAccountId` (optional) is the account the agent is CURRENTLY running on —
200
241
  * that card gets an "In use" marker so "active" (healthy) reads distinct from
@@ -206,7 +247,15 @@ export function renderAccounts(doc, target, accounts, now = Date.now(), inUseAcc
206
247
  target.appendChild(el(doc, 'div', 'sub-empty', 'No subscription accounts enrolled yet.'));
207
248
  return;
208
249
  }
209
- for (const a of accounts) {
250
+ for (const a of groupAccountsByProvider(accounts)) {
251
+ // A group marker, not an account: render the provider heading and continue.
252
+ // (The operator's complaint was a flat interleaved list — Claude and Codex
253
+ // accounts mixed together with only a small per-card meta line to tell them
254
+ // apart. The heading is what makes the split scannable.)
255
+ if (a && a.__providerHeading) {
256
+ target.appendChild(el(doc, 'div', 'sub-provider-heading', a.__providerHeading));
257
+ continue;
258
+ }
210
259
  const inUse = !!(inUseAccountId && a && a.id === inUseAccountId);
211
260
  const card = el(doc, 'div', inUse ? 'sub-account sub-account-inuse' : 'sub-account');
212
261
  const head = el(doc, 'div', 'sub-account-head');
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
5
- "packageVersion": "1.3.1169",
5
+ "packageVersion": "1.3.1170",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "ee1555f5e0d6589d8b1b4fdf24ebe91f6435aca13c9e8f4df5edeebb1338e196",
2
+ "sha256": "a280711e801cac1066b109eb5400956a867d0028f6b5825ad21b7b1ccde1f49f",
3
3
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
4
- "packageVersion": "1.3.1169"
4
+ "packageVersion": "1.3.1170"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
3
3
  "articleCount": 88,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1169"
5
+ "packageVersion": "1.3.1170"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.1169",
3
+ "version": "1.3.1170",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-08-16T01:19:21.033Z",
5
- "instarVersion": "1.3.1169",
4
+ "generatedAt": "2026-08-16T03:47:55.829Z",
5
+ "instarVersion": "1.3.1170",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
5
- "packageVersion": "1.3.1169",
5
+ "packageVersion": "1.3.1170",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "ee1555f5e0d6589d8b1b4fdf24ebe91f6435aca13c9e8f4df5edeebb1338e196",
2
+ "sha256": "a280711e801cac1066b109eb5400956a867d0028f6b5825ad21b7b1ccde1f49f",
3
3
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
4
- "packageVersion": "1.3.1169"
4
+ "packageVersion": "1.3.1170"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
3
3
  "articleCount": 88,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1169"
5
+ "packageVersion": "1.3.1170"
6
6
  }
@@ -0,0 +1,55 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The Subscriptions tab now groups your accounts by provider — Claude accounts together, Codex
9
+ accounts together, each under a heading.
10
+
11
+ Before, the list was one flat run of cards in enrolment order, so a Codex account could sit
12
+ between two Claude ones. Each card names its provider, but in small text partway down, so
13
+ telling them apart meant reading every card. With a single provider that was fine. With two it
14
+ stopped being fine.
15
+
16
+ The quota bars live inside the account cards, so grouping the accounts groups the quota view
17
+ along with them.
18
+
19
+ ## What to Tell Your User
20
+
21
+ - "Your accounts list is grouped by provider now, so your Codex account no longer sits in the
22
+ middle of your Claude ones."
23
+ - "If you only have one provider, nothing changes at all."
24
+
25
+ ## Summary of New Capabilities
26
+
27
+ None. This is a presentation change to an existing tab — no endpoint, no configuration.
28
+
29
+ ## Evidence
30
+
31
+ 8 tests in the existing jsdom suite, which exercises the shipped dashboard module against a real
32
+ DOM. Shown capable of failing: restoring the flat list fails exactly the four grouping
33
+ assertions while the four invariant tests keep passing.
34
+
35
+ The controls carry the weight — a single-provider list is asserted unchanged, and order within a
36
+ group is asserted preserved. Without them, "grouping works" would pass equally well against a
37
+ change that sorted everything alphabetically and added a redundant heading to every
38
+ single-provider install.
39
+
40
+ The heading is the one new dynamic string reaching the page, so it rides the tab's existing
41
+ sanitize-and-write-as-text contract; a test plants a hostile provider string and confirms no
42
+ live element and no event-handler attribute survives.
43
+
44
+ ## Compatibility Notes
45
+
46
+ **A single-provider install is untouched**: with one provider no heading is drawn, and the list
47
+ renders exactly as before. That is deliberate — a heading reading "Claude" above a list of only
48
+ Claude accounts is noise — and it is pinned by a test.
49
+
50
+ Accounts keep their existing order within each group. Nothing is sorted alphabetically, because
51
+ the order accounts arrive in carries meaning the dashboard cannot see (which was enrolled first,
52
+ which is the default). The change re-associates without re-ranking.
53
+
54
+ An account with no provider is still shown, under a heading reading "Other", rather than being
55
+ dropped or filed under a provider it does not belong to.
@@ -0,0 +1,70 @@
1
+ # Side-Effects Review — group the accounts list by provider
2
+
3
+ ## Summary
4
+
5
+ `renderAccounts` drew accounts in a flat pool-ordered run, interleaving providers. It now draws
6
+ each provider's accounts together under a heading, via a new pure `groupAccountsByProvider`.
7
+
8
+ ## Decision-point inventory
9
+
10
+ None. This is presentation ordering. It gates nothing, grants nothing, and reads no state
11
+ beyond the accounts array it is already handed.
12
+
13
+ ## 1. Over-block / 2. Under-block
14
+
15
+ Not applicable — no admission decision. The nearest analogue is dropping an account from the
16
+ list, which a test forbids explicitly (every account renders exactly once; count asserted).
17
+
18
+ ## 3. Level-of-abstraction fit
19
+
20
+ Grouping belongs in the renderer, not the API. The pool's order is meaningful and the server
21
+ should keep returning it as-is; how it is PRESENTED is a dashboard concern. Sorting server-side
22
+ would have imposed one view on every consumer.
23
+
24
+ ## 4. Signal vs authority
25
+
26
+ Not applicable — no decision logic.
27
+
28
+ ## 5. Interactions
29
+
30
+ The in-use marker, quota bars, status, email and the no-quota fallback all render through the
31
+ unchanged card path; grouping only reorders and interleaves headings. The heading marker is a
32
+ sentinel object (`__providerHeading`) consumed at the top of the same loop, so there is one
33
+ card-rendering path, not two. A test asserts the in-use marker survives grouping.
34
+
35
+ ## 6. Multi-machine posture
36
+
37
+ Machine-local BY DESIGN — this is dashboard rendering of data the server already merged.
38
+ Nothing replicates. The pool view's own machine tagging is untouched.
39
+
40
+ ## 7. Failure modes
41
+
42
+ `groupAccountsByProvider` handles a non-array (returns []), a missing provider (grouped under
43
+ `Other`), and a non-string provider (coerced to the empty key). It has no I/O and cannot throw
44
+ for any accounts array the existing renderer would have accepted.
45
+
46
+ **Security**: the heading is the only new dynamic string reaching the DOM. It goes through
47
+ `friendlyProvider`, which maps known providers and sanitizes everything else, and is written
48
+ via the module's `el()` textContent helper. A test plants `<img src=x onerror=...>` as a
49
+ provider and asserts no live element and no `onerror` attribute survives.
50
+
51
+ ## 8. Rollback cost
52
+
53
+ Change one call site back to `for (const a of accounts)`. No state, no migration, no config.
54
+
55
+ ## Evidence
56
+
57
+ 8 new tests in the existing jsdom suite, which drives the SHIPPED `dashboard/subscriptions.js`
58
+ against a real DOM. Shown capable of failing: reverting the call site fails exactly the 4
59
+ grouping assertions while the 4 invariant tests keep passing — the correct signature, since
60
+ those describe behaviour that must hold either way.
61
+
62
+ Controls carry the weight here: a single-provider list is asserted UNCHANGED (no heading), and
63
+ account order within a group is asserted preserved. Without those, "grouping works" would pass
64
+ equally well against a change that sorted everything alphabetically and added a redundant
65
+ heading to every single-provider install.
66
+
67
+ One test was WRONG on first run and the code was right: it asserted `innerHTML` lacks the
68
+ substring "onerror", but correctly-escaped text still reads `&lt;img src=x onerror=...&gt;`.
69
+ The assertion now tests the real property — no live element, no such attribute, heading has no
70
+ element children — rather than the absence of characters.