instar 1.3.786 → 1.3.788

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.
@@ -55,10 +55,27 @@
55
55
  position: relative;
56
56
  }
57
57
 
58
- /* Hamburger togglevisible only on mobile, sits in the header row */
58
+ /* Navigation menu button grouped-nav trigger at ALL widths (F2, dashboard-ux-standard).
59
+ Reachability floor: 25 tabs can't fit a flat bar, so a single grouped menu opens from here. */
59
60
  .nav-toggle {
60
- display: none;
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ margin-left: 16px;
65
+ padding: 8px 14px;
66
+ min-height: 38px;
67
+ background: var(--bg);
68
+ border: 1px solid var(--border);
69
+ border-radius: 6px;
70
+ color: var(--text);
71
+ font-size: 14px;
72
+ font-weight: 500;
73
+ cursor: pointer;
74
+ flex-shrink: 0;
61
75
  }
76
+ .nav-toggle:hover { background: var(--bg-hover); }
77
+ .nav-toggle[aria-expanded="true"] { background: var(--bg-active); border-color: var(--accent); }
78
+ #navToggleLabel { font-weight: 600; color: var(--text-bright); }
62
79
 
63
80
  .header-left {
64
81
  display: flex;
@@ -527,6 +544,33 @@
527
544
  background: var(--bg);
528
545
  }
529
546
 
547
+ /* ── REQUIRED placement for every tab panel declared after </main> ──
548
+ Those panels are direct .app grid children. switchTab() hides the
549
+ sidebar too, so WITHOUT explicit grid placement the lone visible
550
+ panel auto-places into the 280px sidebar COLUMN — the "squished to
551
+ the side" bug (operator report 2026-07-08). Every after-main panel
552
+ MUST carry class="tab-panel" (or one of the container classes that
553
+ already sets grid-column: files/jobs/systems/features/dropzone, or
554
+ ph-root). Enforced structurally:
555
+ tests/unit/dashboard-panel-placement.test.ts fails on a bare panel. */
556
+ .tab-panel {
557
+ grid-column: 1 / -1;
558
+ min-width: 0;
559
+ background: var(--bg);
560
+ }
561
+
562
+ /* F3 — Dashboard UX Standard: every tab's plain-language purpose line.
563
+ One muted, readable sentence directly under the tab header telling a
564
+ non-expert what the tab is for and what they can do here. Enforced by
565
+ tests/unit/dashboard-tab-purpose.test.ts. */
566
+ .tab-purpose {
567
+ font-size: 13px;
568
+ color: var(--text-dim);
569
+ line-height: 1.5;
570
+ margin: 0 0 4px 0;
571
+ max-width: 900px;
572
+ }
573
+
530
574
  .terminal-header {
531
575
  display: flex;
532
576
  align-items: center;
@@ -752,41 +796,69 @@
752
796
  ::-webkit-scrollbar-thumb:hover { background: #444; }
753
797
 
754
798
  /* ── Tab bar ──────────────────────────────────────────── */
799
+ /* Grouped navigation menu (F2, dashboard-ux-standard) — one dropdown at ALL widths,
800
+ opened by .nav-toggle. Hidden until .app.nav-open. Replaces the old horizontal bar
801
+ that clipped 17 of 25 tabs with no reachable affordance. */
755
802
  .tab-bar {
756
- display: flex;
757
- gap: 0;
758
- margin-left: 24px;
759
- overflow-x: auto;
760
- -webkit-overflow-scrolling: touch;
761
- scrollbar-width: none; /* hide scrollbar on Firefox */
803
+ position: absolute;
804
+ top: 100%;
805
+ left: 16px;
806
+ width: 320px;
807
+ max-width: calc(100vw - 32px);
808
+ display: none;
809
+ flex-direction: column;
810
+ gap: 2px;
811
+ padding: 8px;
812
+ background: var(--bg-panel);
813
+ border: 1px solid var(--border);
814
+ border-radius: 0 0 10px 10px;
815
+ box-shadow: 0 8px 28px rgba(0,0,0,0.45);
816
+ z-index: 100;
817
+ max-height: calc(100vh - 64px);
818
+ overflow-y: auto;
762
819
  }
763
- .tab-bar::-webkit-scrollbar { display: none; }
820
+ .app.nav-open .tab-bar { display: flex; }
764
821
 
765
- .tab-bar .tab {
766
- padding: 6px 16px;
767
- border: 1px solid var(--border);
768
- border-radius: 6px 6px 0 0;
769
- background: var(--bg);
822
+ /* Group headers inside the menu (grouped sections). */
823
+ .tab-group { display: flex; flex-direction: column; gap: 2px; }
824
+ .tab-group + .tab-group { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--border); }
825
+ .tab-group-label {
826
+ font-size: 11px;
827
+ font-weight: 700;
828
+ letter-spacing: 0.06em;
829
+ text-transform: uppercase;
770
830
  color: var(--text-dim);
771
- font-size: 13px;
831
+ padding: 4px 10px 2px;
832
+ }
833
+
834
+ .tab-bar .tab {
835
+ display: flex;
836
+ align-items: center;
837
+ justify-content: space-between;
838
+ gap: 8px;
839
+ width: 100%;
840
+ text-align: left;
841
+ padding: 9px 12px;
842
+ min-height: 40px;
843
+ border: 1px solid transparent;
844
+ border-radius: 6px;
845
+ background: transparent;
846
+ color: var(--text);
847
+ font-size: 14px;
772
848
  font-weight: 500;
773
849
  cursor: pointer;
774
- border-bottom: none;
775
- margin-bottom: -1px;
776
850
  transition: color 0.15s, background 0.15s;
777
- white-space: nowrap;
778
- flex-shrink: 0;
779
851
  }
780
852
 
781
853
  .tab-bar .tab:hover {
782
- color: var(--text);
854
+ color: var(--text-bright);
783
855
  background: var(--bg-hover);
784
856
  }
785
857
 
786
858
  .tab-bar .tab.active {
787
- background: var(--bg-panel);
859
+ background: var(--bg-active);
788
860
  color: var(--accent);
789
- border-color: var(--border);
861
+ border-color: var(--accent);
790
862
  }
791
863
 
792
864
  .tab-bar .tab-count {
@@ -884,10 +956,10 @@
884
956
  maturation "you're here" marker. */
885
957
  .ph-root {
886
958
  --ph-accent: #4ade80;
887
- /* These panels are declared after </main>, making them direct .app grid
888
- children. switchTab() hides the sidebar too, so without explicit
889
- placement the lone visible panel auto-places into the 280px sidebar
890
- COLUMN the "squashed to the side" bug. Span the full grid. */
959
+ /* Grid placement for after-main panels is now the shared .tab-panel
960
+ contract (see the .tab-panel rule + the placement floor test:
961
+ tests/unit/dashboard-panel-placement.test.ts). ph-root keeps its own
962
+ grid-column because it ALSO constrains width for prose readability. */
891
963
  grid-column: 1 / -1;
892
964
  max-width: 760px;
893
965
  width: 100%;
@@ -993,6 +1065,7 @@
993
1065
  .files-container {
994
1066
  display: flex;
995
1067
  grid-column: 1 / -1;
1068
+ min-width: 0; /* F4 — a grid child must be able to shrink below content width, else it blows out the row and the body scrolls sideways */
996
1069
  overflow: hidden;
997
1070
  background: var(--bg);
998
1071
  }
@@ -1804,6 +1877,7 @@
1804
1877
  .jobs-container {
1805
1878
  display: flex;
1806
1879
  grid-column: 1 / -1;
1880
+ min-width: 0; /* F4 — grid child must shrink below content width (no body h-scroll) */
1807
1881
  overflow: hidden;
1808
1882
  background: var(--bg);
1809
1883
  }
@@ -2178,6 +2252,7 @@
2178
2252
  /* ── Features Tab (was Discovery) ──────────────────────────── */
2179
2253
  .features-container {
2180
2254
  grid-column: 1 / -1;
2255
+ min-width: 0; /* F4 — grid child must shrink below content width (no body h-scroll) */
2181
2256
  overflow-y: auto;
2182
2257
  background: var(--bg);
2183
2258
  padding: 20px;
@@ -2269,7 +2344,7 @@
2269
2344
  .feat-reversibility { font-size: 12px; color: var(--text-dim); line-height: 1.5; padding: 12px 14px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px; }
2270
2345
 
2271
2346
  /* ── Systems Tab ──────────────────────────────────────────── */
2272
- .systems-container { grid-column: 1 / -1; overflow-y: auto; background: var(--bg); padding: 20px; }
2347
+ .systems-container { grid-column: 1 / -1; min-width: 0; overflow-y: auto; background: var(--bg); padding: 20px; }
2273
2348
  .systems-main { max-width: 960px; margin: 0 auto; }
2274
2349
  .systems-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
2275
2350
  .systems-header h2 { font-size: 16px; font-weight: 600; color: var(--text-bright); }
@@ -2735,32 +2810,47 @@
2735
2810
  <img class="logo" src="/dashboard/logo.png" alt="Instar">
2736
2811
  <h1>Instar Dashboard</h1>
2737
2812
  <button class="nav-toggle" id="navToggle" aria-label="Open navigation menu" aria-expanded="false" onclick="toggleNavMenu()">&#9776; <span id="navToggleLabel">Sessions</span></button>
2738
- <nav class="tab-bar" id="tabBar">
2739
- <button class="tab active" data-tab="sessions" onclick="switchTab('sessions')">Sessions <span class="tab-count" id="tabSessionCount">0</span></button>
2740
- <button class="tab" data-tab="files" onclick="switchTab('files')">Files</button>
2741
- <button class="tab" data-tab="dropzone" onclick="switchTab('dropzone')">Send Content</button>
2742
- <button class="tab" data-tab="secrets" onclick="switchTab('secrets')">Secrets <span class="tab-count" id="tabSecretCount">0</span></button>
2743
- <button class="tab" data-tab="jobs" onclick="switchTab('jobs')">Jobs <span class="tab-count" id="tabJobCount">0</span></button>
2744
- <button class="tab" data-tab="features" onclick="switchTab('features')">Features</button>
2745
- <button class="tab" data-tab="systems" onclick="switchTab('systems')">Health</button>
2746
- <button class="tab" data-tab="integrated-being" onclick="switchTab('integrated-being')">Integrated-Being</button>
2747
- <button class="tab" data-tab="pr-pipeline" onclick="switchTab('pr-pipeline')">PR Pipeline</button>
2748
- <button class="tab" data-tab="projects" onclick="switchTab('projects')">Projects <span class="tab-count" id="tabProjectCount">0</span></button>
2749
- <button class="tab" data-tab="initiatives" onclick="switchTab('initiatives')">Initiatives <span class="tab-count" id="tabInitiativeCount">0</span></button>
2750
- <button class="tab" data-tab="commitments" onclick="switchTab('commitments')">Commitments <span class="tab-count" id="tabCommitmentCount">0</span></button>
2751
- <button class="tab" data-tab="tokens" onclick="switchTab('tokens')">Tokens</button>
2752
- <button class="tab" data-tab="resources" onclick="switchTab('resources')">Resource Usage</button>
2753
- <button class="tab" data-tab="llm-activity" onclick="switchTab('llm-activity')">LLM Activity</button>
2754
- <button class="tab" data-tab="routing-map" onclick="switchTab('routing-map')">Routing Map</button>
2755
- <button class="tab" data-tab="spend" onclick="switchTab('spend')">Spend</button>
2756
- <button class="tab" data-tab="threadline" onclick="switchTab('threadline')">Threadline</button>
2757
- <button class="tab" data-tab="evidence" onclick="switchTab('evidence')">Evidence</button>
2758
- <button class="tab" data-tab="process-health" onclick="switchTab('process-health')">Process Health</button>
2759
- <button class="tab" data-tab="subscriptions" onclick="switchTab('subscriptions')">Subscriptions</button>
2760
- <button class="tab" data-tab="preferences-learning" onclick="switchTab('preferences-learning')">Preferences</button>
2761
- <button class="tab" data-tab="machines" onclick="switchTab('machines')">Machines</button>
2762
- <button class="tab" data-tab="mandates" onclick="switchTab('mandates')">Mandates</button>
2763
- <button class="tab" data-tab="blockers" onclick="switchTab('blockers')">Blockers</button>
2813
+ <nav class="tab-bar" id="tabBar" aria-label="Dashboard sections">
2814
+ <div class="tab-group" role="group" aria-label="Runtime">
2815
+ <div class="tab-group-label">Runtime</div>
2816
+ <button class="tab active" data-tab="sessions" onclick="switchTab('sessions')">Sessions <span class="tab-count" id="tabSessionCount">0</span></button>
2817
+ <button class="tab" data-tab="machines" onclick="switchTab('machines')">Machines</button>
2818
+ <button class="tab" data-tab="jobs" onclick="switchTab('jobs')">Jobs <span class="tab-count" id="tabJobCount">0</span></button>
2819
+ <button class="tab" data-tab="systems" onclick="switchTab('systems')">Health</button>
2820
+ </div>
2821
+ <div class="tab-group" role="group" aria-label="Work & Delivery">
2822
+ <div class="tab-group-label">Work &amp; Delivery</div>
2823
+ <button class="tab" data-tab="pr-pipeline" onclick="switchTab('pr-pipeline')">PR Pipeline</button>
2824
+ <button class="tab" data-tab="projects" onclick="switchTab('projects')">Projects <span class="tab-count" id="tabProjectCount">0</span></button>
2825
+ <button class="tab" data-tab="initiatives" onclick="switchTab('initiatives')">Initiatives <span class="tab-count" id="tabInitiativeCount">0</span></button>
2826
+ <button class="tab" data-tab="commitments" onclick="switchTab('commitments')">Commitments <span class="tab-count" id="tabCommitmentCount">0</span></button>
2827
+ <button class="tab" data-tab="blockers" onclick="switchTab('blockers')">Blockers</button>
2828
+ <button class="tab" data-tab="evidence" onclick="switchTab('evidence')">Evidence</button>
2829
+ </div>
2830
+ <div class="tab-group" role="group" aria-label="Cost & Routing">
2831
+ <div class="tab-group-label">Cost &amp; Routing</div>
2832
+ <button class="tab" data-tab="spend" onclick="switchTab('spend')">Spend</button>
2833
+ <button class="tab" data-tab="subscriptions" onclick="switchTab('subscriptions')">Subscriptions</button>
2834
+ <button class="tab" data-tab="routing-map" onclick="switchTab('routing-map')">Routing Map</button>
2835
+ <button class="tab" data-tab="llm-activity" onclick="switchTab('llm-activity')">LLM Activity</button>
2836
+ <button class="tab" data-tab="tokens" onclick="switchTab('tokens')">Tokens</button>
2837
+ <button class="tab" data-tab="resources" onclick="switchTab('resources')">Resource Usage</button>
2838
+ </div>
2839
+ <div class="tab-group" role="group" aria-label="Intelligence & Comms">
2840
+ <div class="tab-group-label">Intelligence &amp; Comms</div>
2841
+ <button class="tab" data-tab="threadline" onclick="switchTab('threadline')">Threadline</button>
2842
+ <button class="tab" data-tab="integrated-being" onclick="switchTab('integrated-being')">Integrated-Being</button>
2843
+ <button class="tab" data-tab="process-health" onclick="switchTab('process-health')">Process Health</button>
2844
+ <button class="tab" data-tab="preferences-learning" onclick="switchTab('preferences-learning')">Preferences</button>
2845
+ </div>
2846
+ <div class="tab-group" role="group" aria-label="Content & Access">
2847
+ <div class="tab-group-label">Content &amp; Access</div>
2848
+ <button class="tab" data-tab="files" onclick="switchTab('files')">Files</button>
2849
+ <button class="tab" data-tab="dropzone" onclick="switchTab('dropzone')">Send Content</button>
2850
+ <button class="tab" data-tab="secrets" onclick="switchTab('secrets')">Secrets <span class="tab-count" id="tabSecretCount">0</span></button>
2851
+ <button class="tab" data-tab="features" onclick="switchTab('features')">Features</button>
2852
+ <button class="tab" data-tab="mandates" onclick="switchTab('mandates')">Mandates</button>
2853
+ </div>
2764
2854
  </nav>
2765
2855
  </div>
2766
2856
  <div class="vital-signs" id="vitalSigns">
@@ -2800,6 +2890,7 @@
2800
2890
  <button class="new-session-btn" onclick="openNewSessionModal()" title="New session">+</button>
2801
2891
  </div>
2802
2892
  </div>
2893
+ <div class="tab-purpose" style="padding:0 12px 8px">Your agent's live sessions — pick one to watch its terminal in real time.</div>
2803
2894
  <div class="session-list" id="sessionList">
2804
2895
  <div class="empty-state" id="emptyState">
2805
2896
  <div class="icon">&#x1F4AD;</div>
@@ -2852,6 +2943,7 @@
2852
2943
  <div class="file-tree-header">
2853
2944
  <h2>Files</h2>
2854
2945
  </div>
2946
+ <div class="tab-purpose" style="padding:0 12px">Browse and edit this project's files from any device — changes save straight to disk.</div>
2855
2947
  <div class="file-tree-list" id="fileTreeList">
2856
2948
  <div class="file-loading" id="fileTreeLoading">Loading...</div>
2857
2949
  </div>
@@ -2966,7 +3058,7 @@
2966
3058
  <option value="lastRun">Sort: Last Run</option>
2967
3059
  </select>
2968
3060
  </div>
2969
- <div style="font-size:11px;color:var(--text-dim);padding:0 12px 8px;line-height:1.4">Monitor and control your scheduled tasks</div>
3061
+ <div class="tab-purpose" style="padding:0 12px 8px">Monitor and control your scheduled tasks — recurring jobs that run on their own.</div>
2970
3062
  <div id="jobsSummary" class="jobs-summary"></div>
2971
3063
  <div class="jobs-filter-bar" id="jobsFilterBar">
2972
3064
  <button class="jobs-filter-chip active" data-filter="all" onclick="setJobFilter('all')">All</button>
@@ -2990,13 +3082,14 @@
2990
3082
  </div>
2991
3083
 
2992
3084
  <!-- Integrated-Being Tab (Integrated-Being v1) -->
2993
- <div id="integratedBeingPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3085
+ <div id="integratedBeingPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
2994
3086
  <div style="display:flex;justify-content:space-between;align-items:center">
2995
3087
  <h2 style="margin:0">Integrated-Being</h2>
2996
3088
  <div>
2997
3089
  <button onclick="loadIntegratedBeing()" style="padding:6px 12px">Refresh</button>
2998
3090
  </div>
2999
3091
  </div>
3092
+ <div class="tab-purpose">A cross-session record of what other parts of this agent have been doing — the shared memory that keeps separate conversations aware of each other.</div>
3000
3093
  <div id="ibPerMachineNotice" style="font-size:12px;color:var(--text-dim);display:none">
3001
3094
  Integrated-Being on this machine only — cross-machine ledger visibility is not yet implemented.
3002
3095
  </div>
@@ -3111,13 +3204,14 @@
3111
3204
  <!-- Read-only: NO action buttons that mutate eligibility. All PR-authored -->
3112
3205
  <!-- content rendered via textContent, never innerHTML — XSS defense rule -->
3113
3206
  <!-- for the dashboard per spec §"Dashboard surface rules". -->
3114
- <div id="prPipelinePanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3207
+ <div id="prPipelinePanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3115
3208
  <div style="display:flex;justify-content:space-between;align-items:center">
3116
3209
  <h2 style="margin:0">PR Pipeline</h2>
3117
3210
  <div>
3118
3211
  <button onclick="loadPrPipeline()" style="padding:6px 12px">Refresh</button>
3119
3212
  </div>
3120
3213
  </div>
3214
+ <div class="tab-purpose">A live view of your open pull requests as they move through review and CI checks toward merge.</div>
3121
3215
  <div id="prPipelinePhaseNotice" style="font-size:12px;color:var(--text-dim)"></div>
3122
3216
  <div id="prPipelineEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
3123
3217
  No PR activity to display.
@@ -3126,13 +3220,13 @@
3126
3220
  </div>
3127
3221
 
3128
3222
  <!-- Projects Tab — PROJECT-SCOPE-SPEC Phase 1.10 read-only view -->
3129
- <div id="projectsPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3223
+ <div id="projectsPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3130
3224
  <div style="display:flex;justify-content:space-between;align-items:center">
3131
3225
  <h2 style="margin:0">Projects</h2>
3132
3226
  <button onclick="loadProjects()" style="padding:6px 12px">Refresh</button>
3133
3227
  </div>
3134
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3135
- Multi-spec project plans. Each project bundles related child initiatives across rounds with structurally-gated round advance.
3228
+ <div class="tab-purpose">
3229
+ Multi-spec project plans. Each project groups related pieces of work and only advances to its next round once its required checks pass.
3136
3230
  </div>
3137
3231
  <div id="projectsEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
3138
3232
  No active projects. Create one via <code>POST /projects</code> with a plan-doc path.
@@ -3141,7 +3235,7 @@
3141
3235
  </div>
3142
3236
 
3143
3237
  <!-- Initiatives Tab — multi-phase long-running work -->
3144
- <div id="initiativesPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3238
+ <div id="initiativesPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3145
3239
  <div style="display:flex;justify-content:space-between;align-items:center">
3146
3240
  <h2 style="margin:0">Initiatives</h2>
3147
3241
  <div style="display:flex;gap:8px;align-items:center">
@@ -3158,8 +3252,8 @@
3158
3252
  <button onclick="loadInitiatives()" style="padding:6px 12px">Refresh</button>
3159
3253
  </div>
3160
3254
  </div>
3161
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3162
- Multi-phase long-running work. Signals: <span style="color:#e27d3b">needs-user</span>,
3255
+ <div class="tab-purpose">
3256
+ Long-running work that spans multiple phases, with a status flag on each. Signals: <span style="color:#e27d3b">needs-user</span>,
3163
3257
  <span style="color:#d65f5f">next-check-due</span>,
3164
3258
  <span style="color:#4a9a4a">ready-to-advance</span>,
3165
3259
  <span style="color:#888">stale (&gt;7d)</span>.
@@ -3172,7 +3266,7 @@
3172
3266
  </div>
3173
3267
 
3174
3268
  <!-- Commitments Tab (Open Promises) -->
3175
- <div id="secretsPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3269
+ <div id="secretsPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3176
3270
  <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
3177
3271
  <h2 style="margin:0">Secrets</h2>
3178
3272
  <div style="display:flex;gap:8px">
@@ -3180,9 +3274,8 @@
3180
3274
  <button onclick="loadSecrets()" style="padding:6px 12px">Refresh</button>
3181
3275
  </div>
3182
3276
  </div>
3183
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3184
- Secret Drop — one-time-use links for users to submit credentials safely (never through chat).
3185
- Links expire automatically and are destroyed after submission.
3277
+ <div class="tab-purpose">
3278
+ Secret Drop — create one-time links so someone can hand you a password or API key safely, never pasted into chat. Each link expires and is destroyed the moment it's used.
3186
3279
  </div>
3187
3280
  <div id="secretsEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
3188
3281
  No pending secret requests.
@@ -3190,12 +3283,12 @@
3190
3283
  <div id="secretsList" style="display:flex;flex-direction:column;gap:12px"></div>
3191
3284
  </div>
3192
3285
 
3193
- <div id="commitmentsPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3286
+ <div id="commitmentsPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3194
3287
  <div style="display:flex;justify-content:space-between;align-items:center">
3195
3288
  <h2 style="margin:0">Commitments</h2>
3196
3289
  <button onclick="loadCommitments()" style="padding:6px 12px">Refresh</button>
3197
3290
  </div>
3198
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3291
+ <div class="tab-purpose">
3199
3292
  Open promises — beacon-watched commitments (⏳). States:
3200
3293
  <span style="color:#4a9a4a">pending</span>,
3201
3294
  <span style="color:#e27d3b">atRisk</span>,
@@ -3208,12 +3301,12 @@
3208
3301
  </div>
3209
3302
 
3210
3303
  <!-- Tokens Tab — read-only token-usage observability -->
3211
- <div id="tokensPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3304
+ <div id="tokensPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3212
3305
  <div style="display:flex;justify-content:space-between;align-items:center">
3213
3306
  <h2 style="margin:0">Tokens</h2>
3214
3307
  <button onclick="loadTokens()" style="padding:6px 12px">Refresh</button>
3215
3308
  </div>
3216
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3309
+ <div class="tab-purpose">
3217
3310
  Token usage rolled up from Claude Code session transcripts. Last 24 hours unless noted.
3218
3311
  </div>
3219
3312
 
@@ -3244,12 +3337,12 @@
3244
3337
 
3245
3338
  <!-- Resource Usage tab — per-agent CPU + memory (ResourceLedger Phase B).
3246
3339
  Read-only observability; mirrors the Tokens tab. -->
3247
- <div id="resourcesPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3340
+ <div id="resourcesPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3248
3341
  <div style="display:flex;justify-content:space-between;align-items:center">
3249
3342
  <h2 style="margin:0">Resource Usage</h2>
3250
3343
  <button onclick="loadResources()" style="padding:6px 12px">Refresh</button>
3251
3344
  </div>
3252
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3345
+ <div class="tab-purpose">
3253
3346
  How much CPU and memory this agent is using right now — the server process and each running session — sampled continuously. Last hour unless noted.
3254
3347
  </div>
3255
3348
 
@@ -3284,12 +3377,12 @@
3284
3377
  true-blocker. A true-blocker is "best current understanding — recheck after <date>",
3285
3378
  never "stop trying". Read-only here; data from GET /blockers. detectedText and all
3286
3379
  free-text are UNTRUSTED and HTML-escaped before render. 503 = feature off. -->
3287
- <div id="blockersPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3380
+ <div id="blockersPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3288
3381
  <div style="display:flex;justify-content:space-between;align-items:center">
3289
3382
  <h2 style="margin:0">Blockers</h2>
3290
3383
  <button onclick="loadBlockers()" style="padding:6px 12px">Refresh</button>
3291
3384
  </div>
3292
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3385
+ <div class="tab-purpose">
3293
3386
  Things that looked like they stopped progress, tracked as <b>decaying hypotheses</b> rather than settled walls.
3294
3387
  Each one moves through verification states; a "true blocker" is the best current understanding with a recheck date —
3295
3388
  never a permanent "give up".
@@ -3303,7 +3396,7 @@
3303
3396
  Every LLM call the system makes on its own behalf: which component, which
3304
3397
  provider/model, whether it acted or no-op'd, was skipped, cost + latency.
3305
3398
  Read-only; data from /metrics/features. -->
3306
- <div id="llmActivityPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3399
+ <div id="llmActivityPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3307
3400
  <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
3308
3401
  <h2 style="margin:0">LLM Activity</h2>
3309
3402
  <div style="display:flex;gap:8px;align-items:center">
@@ -3315,7 +3408,7 @@
3315
3408
  <button onclick="loadLlmActivity()" style="padding:6px 12px">Refresh</button>
3316
3409
  </div>
3317
3410
  </div>
3318
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3411
+ <div class="tab-purpose">
3319
3412
  Every decision the system's "autopilot" makes with an AI call — sentinels, gates, background checks.
3320
3413
  For each one: which AI provider ran it, how often it actually <b>acted</b> on something vs. found
3321
3414
  nothing to do, how often it was <b>skipped</b> to save on rate limits, and what it cost. A row that
@@ -3344,12 +3437,12 @@
3344
3437
  model it uses and its full ordered fallback tier list, with injection-safe /
3345
3438
  money-gated / critical-gate flags. Read-only display; data from
3346
3439
  /intelligence/routing/chains. No controls — the map only DISPLAYS the routing. -->
3347
- <div id="routingMapPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3440
+ <div id="routingMapPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3348
3441
  <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
3349
3442
  <h2 style="margin:0">Routing Map</h2>
3350
3443
  <button onclick="loadRoutingMap()" style="padding:6px 12px">Refresh</button>
3351
3444
  </div>
3352
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3445
+ <div class="tab-purpose">
3353
3446
  Where each internal job-kind sends its AI calls. Every background check, gate, and
3354
3447
  sentinel has a <b>lane</b> (its "nature") and a fallback list of <b>doors</b> — the ways
3355
3448
  it can reach a model. This shows, per lane, the ordered door + model list it tries, and
@@ -3379,7 +3472,7 @@
3379
3472
  READ + every metered paid-door key with its caps and honest not-live/$0 state.
3380
3473
  Read-only: data from /routing-spend/summary + /routing-spend/caps. No money controls
3381
3474
  (caps adjust, go-live, alerts are later, dark increments). -->
3382
- <div id="spendPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3475
+ <div id="spendPanel" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3383
3476
  <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
3384
3477
  <h2 style="margin:0">Spend</h2>
3385
3478
  <div style="display:flex;gap:8px;align-items:center">
@@ -3392,7 +3485,7 @@
3392
3485
  <button onclick="loadRoutingSpend()" style="padding:6px 12px">Refresh</button>
3393
3486
  </div>
3394
3487
  </div>
3395
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3488
+ <div class="tab-purpose">
3396
3489
  What the internal AI calls cost, and where the paid-door caps sit. The dollar figures are a
3397
3490
  <b>reporting</b> view — immutable token counts priced against a reviewed price list on read, so a
3398
3491
  price correction reflows automatically. <b>No paid door is live yet</b>, so metered spend is $0;
@@ -3622,12 +3715,12 @@
3622
3715
  </section>
3623
3716
  </div>
3624
3717
 
3625
- <div id="evidenceTab" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3718
+ <div id="evidenceTab" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3626
3719
  <div style="display:flex;justify-content:space-between;align-items:center">
3627
3720
  <h2 style="margin:0">Evidence</h2>
3628
3721
  <div style="font-size:12px;color:var(--text-dim)">Per-entity evidence + reverse citations</div>
3629
3722
  </div>
3630
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3723
+ <div class="tab-purpose">
3631
3724
  Inspect the evidence trail behind any memory entity, or ask "what cites this?" given a kind + sourceId.
3632
3725
  All results are viewer-scope filtered by SemanticMemory before they reach this view.
3633
3726
  </div>
@@ -3681,12 +3774,12 @@
3681
3774
  </div>
3682
3775
 
3683
3776
  <!-- Threadline Tab — agent-to-agent conversations + Telegram bridge settings -->
3684
- <div id="threadlineTab" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3777
+ <div id="threadlineTab" class="tab-panel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
3685
3778
  <div style="display:flex;justify-content:space-between;align-items:center">
3686
3779
  <h2 style="margin:0">Threadline</h2>
3687
3780
  <button onclick="loadThreadlineBridgeConfig()" style="padding:6px 12px">Refresh</button>
3688
3781
  </div>
3689
- <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
3782
+ <div class="tab-purpose">
3690
3783
  Threadline is your agent's secure channel to other AI agents. The Telegram bridge mirrors those conversations into per-thread Telegram topics so you can watch every exchange. Quiet by default — flip the master switch below to opt in.
3691
3784
  </div>
3692
3785
 
@@ -3783,7 +3876,7 @@
3783
3876
  <h2>Health</h2>
3784
3877
  <button class="systems-refresh" onclick="loadSystems()">Refresh</button>
3785
3878
  </div>
3786
- <div style="font-size:12px;color:var(--text-dim);margin-bottom:16px;line-height:1.4">Your agent's health and running processes</div>
3879
+ <div class="tab-purpose" style="margin-bottom:16px">Your agent's health and running processes — the server and each running session at a glance.</div>
3787
3880
  <div id="systemsBanner">
3788
3881
  <div style="padding:20px;color:var(--text-dim);text-align:center">Loading...</div>
3789
3882
  </div>
@@ -5522,7 +5615,7 @@
5522
5615
  <span style="font-size:11px;color:var(--text-dim)">confidence: ${escapeHtml(confidence)}</span>
5523
5616
  <button class="ev-inspect-btn" data-entity-id="${id}" style="margin-left:auto;padding:2px 8px;font-size:11px">Inspect evidence</button>
5524
5617
  </div>
5525
- ${content ? `<div style="font-size:12px;color:var(--text-dim);line-height:1.4">${content}</div>` : ''}
5618
+ ${content ? `<div class="tab-purpose">${content}</div>` : ''}
5526
5619
  </div>
5527
5620
  `;
5528
5621
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.786",
3
+ "version": "1.3.788",
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-07-08T21:14:02.877Z",
5
- "instarVersion": "1.3.786",
4
+ "generatedAt": "2026-07-09T06:42:14.302Z",
5
+ "instarVersion": "1.3.788",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,42 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ - **The "squished to the side" bug is fixed structurally.** Fourteen dashboard tab panels
9
+ (Routing Map, Spend, Tokens, LLM Activity, Integrated Being, PR Pipeline, Projects,
10
+ Initiatives, Secrets, Commitments, Resources, Blockers, Evidence, Threadline) are declared
11
+ after `</main>` and are therefore direct `.app` grid children; with the sidebar hidden they
12
+ auto-placed into the 280px sidebar COLUMN, rendering all content in a ~185px strip on a
13
+ 1280px viewport (operator screenshots, topic 29723). A shared `.tab-panel` class
14
+ (`grid-column: 1 / -1; min-width: 0`) now places every one of them across the full content
15
+ area — one rule instead of fourteen inline copies. The pre-existing `.ph-root` tabs keep
16
+ their own placement + prose width.
17
+ - **A placement floor makes the bug unrepeatable.** `tests/unit/dashboard-panel-placement.test.ts`
18
+ scans the after-main markup region and fails the build when a top-level panel lacks a
19
+ placement-carrying class (with an instructive message naming the panel and the fix), asserts
20
+ the `.tab-panel` rule keeps its grid span, and guards its own matcher with a population floor.
21
+ The old defense was a CSS comment on `.ph-root` — a wish; every panel added since repeated
22
+ the bug. This is the guarantee (Structure > Willpower).
23
+
24
+ ## What to Tell Your User
25
+
26
+ The dashboard tabs that used to render squeezed into a narrow strip on the left of a desktop
27
+ window — including the new Routing Map and Spend tabs — now use the full width of the window.
28
+ Nothing to enable; it's just fixed everywhere once the release lands.
29
+
30
+ ## Summary of New Capabilities
31
+
32
+ - None — this is a layout bug fix. (It also adds a build-time guard so a future dashboard tab
33
+ can't reintroduce the same squished layout.)
34
+
35
+ ## Evidence
36
+
37
+ `tests/unit/dashboard-panel-placement.test.ts` — 4 cases: rule present + spans grid, every
38
+ placement class actually declares grid-column, no bare after-main panel (negative control
39
+ verified: stripping one panel's class fails naming exactly that panel), matcher-anchor guard
40
+ (the slice skips CSS/JS mentions of `</main>`). Real-browser (headless Chromium) before/after
41
+ screenshots at 1280×800: content strip ~260px before, full-width after; 390×844 rendering is
42
+ byte-identical before vs after (placement-only change). All 106 dashboard-reading unit tests green.
@@ -0,0 +1,45 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ - **All 25 dashboard tabs are now reachable — grouped navigation (Dashboard UX Standard F2).**
9
+ The dashboard grew to 25 tabs in a flat horizontal bar that clipped at ~8 tabs on a 1280px
10
+ viewport with a hidden scrollbar — the other ~17 (Spend, Machines, Mandates, Process Health,
11
+ …) were unreachable by pointer, and the active tab often wasn't even visible in the nav
12
+ (operator report, topic 29723). The nav is now ONE grouped dropdown menu, opened from the
13
+ always-visible `☰` button at every viewport, organized into five labeled sections (Runtime,
14
+ Work & Delivery, Cost & Routing, Intelligence & Comms, Content & Access). It reuses the
15
+ existing (previously mobile-only) collapsible-menu machinery — every tab's data-tab, onclick,
16
+ and count badge is preserved verbatim.
17
+ - **The Dashboard UX Standard is now a constitutional standard with CI-enforced floors.**
18
+ `docs/specs/dashboard-ux-standard.md` (operator-approved, topic 29723) turns the operator's
19
+ "VERY user friendly / EASY to navigate" bar into eight objective floors. Two ship enforced:
20
+ F1 (panel placement, shipped #1403) and F2 (nav reachability, this change) — the rest are
21
+ tracked follow-up passes. Registered in `docs/STANDARDS-REGISTRY.md` so the
22
+ Standards-Enforcement-Coverage audit classifies them as enforced gates.
23
+
24
+ ## What to Tell Your User
25
+
26
+ Every dashboard tab is now reachable from one clean, grouped menu (opened by the ☰ button) at
27
+ desktop and phone width — no more tabs hidden off the edge of the screen. Nothing to enable;
28
+ it lands with the release.
29
+
30
+ ## Summary of New Capabilities
31
+
32
+ - None — this is a navigation/UX fix plus a build-time floor (`dashboard-nav-reachability.test.ts`)
33
+ that fails the build if a future tab is unreachable or the nav loses its grouped structure.
34
+
35
+ ## Evidence
36
+
37
+ `tests/unit/dashboard-nav-reachability.test.ts` — 3 cases: every `TAB_REGISTRY` tab has a
38
+ reachable nav control and there are no orphan nav buttons (population floor ≥20), the nav is
39
+ grouped into labeled sections, and the menu is reachable at all widths (`.nav-toggle`
40
+ `display:flex` at base + `.app.nav-open .tab-bar` open mechanism). Real-browser (headless
41
+ Chromium) verified at 1280×860 and 390×844: the grouped menu renders all five sections with all
42
+ 25 tabs, active-tab highlight + count badges intact, closed-state header shows the `☰ Sessions`
43
+ button and a full-width panel; no horizontal body overflow. Both dashboard floor tests green
44
+ (7 cases). The header-logo audit finding was confirmed a static-render artifact (logo.png is
45
+ served live via `express.static`) — corrected in the spec, no spurious fix made.
@@ -0,0 +1,20 @@
1
+ # Side-Effects Review — Dashboard F3 (tab purpose lines)
2
+
3
+ **Change:** Implements floor **F3** of the Dashboard UX Standard (`docs/specs/dashboard-ux-standard.md`, merged in #1404): every registered dashboard tab now carries a plain-language, self-explanatory purpose line near its top. Adds a shared `.tab-purpose` CSS class, converts the dashboard's pre-existing muted intro divs to it (de-jargoning the worst offenders), adds purpose lines to the tabs that lacked one (Sessions, Files), and adds a static floor test.
4
+
5
+ **Files:**
6
+ - `dashboard/index.html` — one `.tab-purpose` CSS rule; ~15 panels' intro divs converted to (or given) `.tab-purpose`; new purpose lines for Sessions and Files. Pure markup/CSS — no script changes.
7
+ - `tests/unit/dashboard-tab-purpose.test.ts` — the F3 floor: maps every `TAB_REGISTRY` tab to its panel markup and fails (naming the tab) if none carries a recognized purpose-line class (`tab-purpose` / `ph-intro` / `features-subtitle` / `dropzone-subtitle`). Population-floor guarded.
8
+
9
+ **Side effects / blast radius:**
10
+ - **Runtime behavior:** NONE. The dashboard is a static file served by `express.static`; this change adds display-only markup + CSS. No JS logic, no API routes, no state, no config touched.
11
+ - **Migrations:** none required. The dashboard `index.html` is served from the package, not templated per-agent; no `PostUpdateMigrator` / CLAUDE.md-template surface is involved (no agent-installed file changes).
12
+ - **Server/lifeline/deploy path:** untouched.
13
+ - **Cross-machine / mesh:** untouched.
14
+ - **Security/PII:** none — no data, credentials, or user content flow through the changed lines.
15
+
16
+ **Risk:** Low. Worst case is a cosmetic mis-placement of a purpose line on a tab, visible only in the dashboard UI, fixable by a follow-up edit. The floor test prevents a future tab from silently shipping without a purpose line.
17
+
18
+ **Rollback:** revert this commit — the dashboard reverts to the prior markup with zero residual state (nothing persisted, nothing migrated).
19
+
20
+ **Follow-ups (tracked, out of scope here):** F4 (mobile no-horizontal-scroll) ships next; F5–F8 (labeled controls, self-explaining empty states, shared component vocabulary that will consolidate the 4 accepted purpose-line classes into one) follow per the spec sequencing.
@@ -0,0 +1,18 @@
1
+ # Side-Effects Review — Dashboard F4 (no mobile horizontal scroll)
2
+
3
+ **Change:** Implements floor **F4** of the Dashboard UX Standard (`docs/specs/dashboard-ux-standard.md`): the page body must never scroll horizontally at 1280/768/390px. The audit (2026-07-08) found panels overflowing the viewport's right edge at 390px so the whole page scrolled sideways on a phone.
4
+
5
+ **Files:**
6
+ - `dashboard/index.html` — adds `min-width: 0` to the four grid-child panel containers that lacked it (`files-container`, `jobs-container`, `systems-container`, `features-container`). These are direct `.app` grid children (`grid-column: 1 / -1`); a CSS grid/flex child defaults to `min-width: auto`, so a wide descendant forces the row wider than the viewport and the body scrolls sideways. `min-width: 0` lets the child shrink so wide content wraps/contains instead of blowing out the row. (`.tab-panel` already carried this from #1403.) No JS touched.
7
+ - `tests/unit/dashboard-viewport-scroll.test.ts` — the F4 floor, in two halves: (1) a STATIC check that every grid-child container carries `min-width: 0` (always runs); (2) a BROWSER-GATED render check that `document.body.scrollWidth <= clientWidth` at each viewport, which SKIPS HONESTLY when no browser driver is installed (FD-2) and runs in the CI browser job. Never a false green.
8
+
9
+ **Side effects / blast radius:**
10
+ - **Runtime behavior:** NONE. Display-only CSS on static markup. No JS, API, state, or config.
11
+ - **Migrations / agent-installed files:** none (dashboard is package-served, not templated per agent).
12
+ - **Server/lifeline/mesh/security:** untouched.
13
+
14
+ **Risk:** Very low. `min-width: 0` on a grid child is the textbook, side-effect-free fix for grid row-blowout; it only *allows* a child to shrink, it never forces a size. Worst case is a wide element inside one of these tabs wrapping instead of overflowing — the desired behavior.
15
+
16
+ **Verification honesty:** The static half is verified green here. The rendered half is **browser-gated and was SKIPPED in this worktree** — no Playwright/Puppeteer is installed, and installing a browser (a large download) was deliberately avoided on a host that wedged twice on 2026-07-08 under filesystem load. The `min-width: 0` fix targets the known mechanism (grid child blowout) with high confidence from the CSS, but a pixel-level 390px confirmation should run via the CI browser job or a browser-capable pass before treating F4 as fully closed. This is disclosed rather than claimed as visually verified.
17
+
18
+ **Rollback:** revert — pure CSS + a test, zero residual state.
@@ -0,0 +1,51 @@
1
+ # Side-Effects Review — Dashboard responsive fix (.tab-panel placement + floor test)
2
+
3
+ **Version / slug:** `dashboard-responsive-fix`
4
+ **Date:** `2026-07-08`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Fixes the "everything is squished to the side" dashboard bug (operator report with screenshots, topic 29723, 2026-07-08): fourteen tab panels declared after `</main>` in `dashboard/index.html` are direct `.app` grid children with no explicit grid placement, so the lone visible panel auto-places into the 280px sidebar column on desktop. The fix adds ONE shared `.tab-panel` CSS class (`grid-column: 1 / -1; min-width: 0`) applied to all fourteen panels (Integrated Being, PR Pipeline, Projects, Initiatives, Secrets, Commitments, Tokens, Resources, Blockers, LLM Activity, Routing Map, Spend, Evidence, Threadline), plus a regression floor test (`tests/unit/dashboard-panel-placement.test.ts`) that fails the build when a future after-main panel lacks a placement-carrying class. The pre-existing `.ph-root` per-tab fix is left untouched; its comment now points at the shared contract. Files touched: `dashboard/index.html`, `tests/unit/dashboard-panel-placement.test.ts`, `docs/specs/dashboard-responsive-fix.eli16.md`, this artifact, and a release fragment.
11
+
12
+ ## Decision-point inventory
13
+
14
+ No decision-point surface. This change is presentation-layer CSS/HTML plus a static test. It gates no information flow, blocks no actions, filters no messages, and constrains no agent behavior at runtime. (The new unit test constrains future COMMITS — the standard test-ratchet pattern — not runtime behavior.)
15
+
16
+ ---
17
+
18
+ ## 1. Over-block
19
+
20
+ No block/allow surface at runtime — over-block not applicable to agent/user traffic. For the commit-time ratchet: the floor test could "over-block" a future legitimate panel that is genuinely out of normal flow (e.g. a new fixed-position overlay). Mitigation is built in: the `OUT_OF_FLOW_EXEMPT` list with a required justification comment, and the failure message names the exact panel and both resolution paths.
21
+
22
+ ## 2. Under-block
23
+
24
+ The floor test keys on 4-space-indented `<div id=...>` openers in the after-`</main>` markup region (all 27 current top-level panels match this shape). A future panel authored at unconventional indentation, as a non-div element, or injected purely from JS would not be seen by the floor. Accepted: the regression vector this floor closes is the observed copy-paste pattern (14 real instances); the population-sanity assertion (≥20 openers visible) fails loudly if the matcher ever goes blind wholesale. Deeper enforcement (viewport smoke tests) belongs to the Dashboard UX Standard work tracked in the topic-29723 working brief. <!-- tracked: topic-29723 dashboard-ux-brief Increment 2 -->
25
+
26
+ ## 3. Level-of-abstraction fit
27
+
28
+ Right layer. The bug is a CSS grid-placement defect; the fix is a shared CSS class at the layout layer where the sibling container classes (`files-container`, `jobs-container`, `systems-container`, `features-container`, `dropzone-container`) already solve the same problem the same way. The alternative — per-panel inline styles — is exactly the pattern that produced fourteen copies of the bug. No smarter existing gate applies (this is not decision logic).
29
+
30
+ ## 4. Signal vs authority compliance
31
+
32
+ Compliant by vacuity at runtime: the change holds no blocking authority and produces no signal — it is inert presentation markup. The unit test is a build-time ratchet, the same class as every other test in `tests/unit/`; it holds commit-blocking authority through CI exactly like all tests do, with deterministic, human-readable failure output.
33
+
34
+ ## 5. Interactions
35
+
36
+ - Interacts with `switchTab()` only passively: switchTab toggles inline `display`; the new class carries grid placement only, so show/hide behavior is unchanged.
37
+ - `.ph-root` panels keep their own placement + 760px prose width; the new rule does not apply to them (no double-fire; the floor test accepts either class).
38
+ - Mobile breakpoint (`@media max-width: 768px`) collapses `.app` to one column; `grid-column: 1 / -1` resolves identically there — verified byte-identical mobile rendering before vs after the change.
39
+ - No other rule targets `.tab-panel` (fresh class name; grep-verified single definition).
40
+
41
+ ## 6. External surfaces
42
+
43
+ Visible change: dashboard tabs render full-width on desktop instead of squished — the operator-requested fix. No API change, no data change, no cross-agent surface, no timing/conversation-state dependency. Verified with real-browser (headless Chromium) before/after screenshots at 1280×800 and 390×844: desktop fixed; mobile rendering byte-identical to pre-change.
44
+
45
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
46
+
47
+ Machine-local BY DESIGN in the same sense as all dashboard markup: the HTML ships identically to every machine with the release; each machine serves its own copy. No replicated state, no machine-boundary URL, no one-voice notice surface. Both of the operator's machines get the fix as they pick up the release.
48
+
49
+ ## 8. Rollback cost
50
+
51
+ Trivial: revert the commit (CSS class + class attributes + one test file). No data migration, no agent state, no config. A revert restores the prior (buggy) rendering and deletes the floor test with it.
@@ -0,0 +1,53 @@
1
+ # Side-Effects Review — Grouped-nav reachability + Dashboard UX Standard (F1/F2)
2
+
3
+ **Version / slug:** `dashboard-ux-standard-nav`
4
+ **Date:** `2026-07-08`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Makes all 25 dashboard tabs reachable (operator report, topic 29723: 17 of 25 unreachable in a clipping flat nav bar) by converting the nav into ONE grouped dropdown menu, opened from the always-visible `☰` toggle at every viewport, organized into five labeled sections. This reuses the existing (previously mobile-only) `.app.nav-open .tab-bar` collapsible-menu machinery — `toggleNavMenu`/`closeNavMenu`/`updateNavToggleLabel`/`switchTab` are untouched; every tab button's `data-tab`, `onclick`, and count badge is preserved verbatim (25 in, 25 out, cross-checked against `TAB_REGISTRY`). Ships the operator-approved `docs/specs/dashboard-ux-standard.md` (8 floors), a `docs/STANDARDS-REGISTRY.md` entry, and the F2 floor test. Files touched: `dashboard/index.html` (CSS: `.nav-toggle` visible at base + `.tab-bar` → grouped dropdown + `.tab-group`/`.tab-group-label`; markup: tabs wrapped in groups), `tests/unit/dashboard-nav-reachability.test.ts`, `docs/specs/dashboard-ux-standard.md`, `docs/STANDARDS-REGISTRY.md`, this artifact, a release fragment.
11
+
12
+ ## Decision-point inventory
13
+
14
+ No runtime decision-point surface. Presentation-layer CSS/HTML plus a static test. It gates no information flow, blocks no actions, filters no messages, constrains no agent behavior at runtime. The new unit test constrains future COMMITS (the standard test-ratchet pattern), not runtime behavior.
15
+
16
+ ---
17
+
18
+ ## 1. Over-block
19
+
20
+ No block/allow surface at runtime. For the commit-time ratchet: the F2 floor could "over-block" a future legitimate nav change — e.g. a deliberate move to a different reachable model (persistent left rail). Mitigation: the floor asserts the FLOOR (every tab reachable, nav grouped, reachable at all widths), not a specific implementation; a left-rail that keeps tabs reachable + grouped still passes. Failure messages name the exact unreachable/orphan tab and the fix.
21
+
22
+ ## 2. Under-block
23
+
24
+ The floor keys on `data-tab="..."` buttons inside the `id="tabBar"` nav and `id: '...'` entries in the `TAB_REGISTRY` array. A tab injected purely from JS (not in the static markup) or a registry authored with unconventional syntax would not be seen. Accepted: the regression vector this closes is the observed flat-nav clip (a real 25-tab overflow); the population floor (≥20 nav buttons AND ≥20 registry ids visible) fails loudly if either matcher goes blind. Deeper enforcement (the F4 viewport smoke test for body-never-h-scroll) is a tracked follow-up pass in the spec's implementation sequencing. <!-- tracked: topic-29723 dashboard-ux-brief Increment 2b -->
25
+
26
+ ## 3. Level-of-abstraction fit
27
+
28
+ Right layer. The bug is nav reachability; the fix reuses the existing collapsible-menu mechanism at the layout layer rather than inventing new nav architecture (a persistent sidebar rewrite would relocate the sessions panel that owns the 280px column — higher risk for no reachability gain). No smarter existing gate applies (not decision logic).
29
+
30
+ ## 4. Signal vs authority compliance
31
+
32
+ Compliant by vacuity at runtime: the change holds no blocking authority and produces no signal — inert presentation markup. The unit test is a build-time ratchet with commit-blocking authority through CI exactly like every other test, with deterministic human-readable failure output.
33
+
34
+ ## 5. Interactions
35
+
36
+ - `switchTab()` already closes the menu (`classList.remove('nav-open')`) and updates the toggle label on selection — verified; the always-dropdown model needs no JS change.
37
+ - The mobile `@media` block still sets `.tab-bar` full-width (`left:0;right:0`) — it reinforces the base dropdown harmlessly (mobile menu = full width).
38
+ - `.tab-panel` placement (F1, #1403) is unaffected — this change touches only the header nav, not the after-main panels.
39
+ - Fresh class names (`.tab-group`, `.tab-group-label`) — grep-verified no prior definitions; `.nav-toggle`/`.tab-bar` rules are the only ones restyled, both verified.
40
+
41
+ ## 6. External surfaces
42
+
43
+ Visible change: the dashboard nav becomes a grouped `☰` menu reachable at every width; all 25 tabs reachable (was ~8). No API change, no data change, no cross-agent surface, no timing/conversation-state dependency. Verified with real-browser (headless Chromium) at 1280×860 (grouped menu: 5 sections, all tabs, active highlight + count badges) and 390×844 (full-width menu, no h-overflow), plus the closed default state (header `☰ Sessions` button + full-width panel).
44
+
45
+ **Operator-Surface-Quality (dashboard is an operator surface):** (1) leads with the primary action — the nav is the surface's primary affordance and is now front-and-center, all destinations reachable; (2) zero raw internals — tabs are plain-language labels, grouped under plain-language section headers, no JSON/IDs/slugs; (3) no destructive action in the nav; (4) plain language a non-engineer reads ("Cost & Routing → Spend"); (5) works at phone width — 40px+ tap targets, readable 14–15px type, no horizontal scroll (headless-verified at 390px). The header-logo "broken" audit finding was confirmed a static-render false positive (logo.png served live via `express.static`) — no spurious change made.
46
+
47
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
48
+
49
+ Machine-local BY DESIGN like all dashboard markup: the HTML ships identically to every machine with the release; each serves its own copy. No replicated state, no machine-boundary URL, no one-voice notice surface. Both operator machines get it as they pick up the release.
50
+
51
+ ## 8. Rollback cost
52
+
53
+ Trivial: revert the commit (CSS + nav markup regroup + one test + the spec/registry/fragment docs). No data migration, no agent state, no config. A revert restores the prior (clipping) nav and deletes the F2 floor with it.