super-backlog 1.3.3 → 1.3.5

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 CHANGED
@@ -1,130 +1,100 @@
1
+ <div align="center">
2
+
3
+ <img src="docs/assets/super-backlog-logo.jpg" alt="Super Backlog" width="100%">
4
+
1
5
  # super-backlog
2
6
 
3
7
  [![CI](https://github.com/adam-s-k-i/super-backlog/actions/workflows/ci.yml/badge.svg)](https://github.com/adam-s-k-i/super-backlog/actions/workflows/ci.yml)
8
+ [![npm](https://img.shields.io/npm/v/super-backlog)](https://www.npmjs.com/package/super-backlog)
4
9
  [![Docs](https://img.shields.io/badge/docs-adam--s--k--i.github.io%2Fsuper--backlog-blue)](https://adam-s-k-i.github.io/super-backlog/)
5
10
 
6
- ![Super Backlog](docs/assets/super-backlog-logo.jpg)
7
-
8
- One command to equip any project with [Backlog.md](https://github.com/MrLesk/Backlog.md) + [Superpowers](https://github.com/obra/superpowers), plus a Project Dashboard.
9
-
10
- ## What & why
11
-
12
- Superpowers and Backlog.md are strong on their own, but nothing wires them together: Superpowers defines *how* agents should work (brainstorming → plans → TDD → review), Backlog.md defines *what* is tracked (markdown tasks, Kanban board, browser UI). Connecting them today means hand-copying glue from project to project — workflow blocks in `AGENTS.md`, bridge skills like `spec-to-backlog`, npm scripts, plugin config. super-backlog is the glue orchestrator that installs and maintains all of it in one step, with a guaranteed-clean exit path (`sbl uninstall`).
13
-
14
- ## Requirements
15
-
16
- - Node >= 20
17
- - A package manager (npm, pnpm, or bun) for dependency installation
18
- - Works with **OpenCode** and **Claude Code** (both configured by default)
11
+ **Supercharge your backlog. Watch it live.**
19
12
 
20
- ## Quickstart
13
+ One command to equip any project with [Backlog.md](https://github.com/MrLesk/Backlog.md) + [Superpowers](https://github.com/obra/superpowers), plus a live Project Dashboard.
21
14
 
22
- Install super-backlog into the current project with the official installer for your platform:
15
+ </div>
23
16
 
24
- **Windows (PowerShell)** — bypasses Execution Policy automatically:
17
+ ---
25
18
 
26
- ```powershell
27
- irm https://raw.githubusercontent.com/adam-s-k-i/super-backlog/master/scripts/install.ps1 | iex
28
- ```
29
-
30
- **macOS / Linux**:
19
+ ## Start in 3 commands
31
20
 
32
21
  ```bash
22
+ # 1 · install (Windows: irm https://raw.githubusercontent.com/adam-s-k-i/super-backlog/master/scripts/install.ps1 | iex)
33
23
  curl -fsSL https://raw.githubusercontent.com/adam-s-k-i/super-backlog/master/scripts/install.sh | bash
34
- ```
35
-
36
- Both installers check Node.js and npm, install `super-backlog`, and run `sbl init` without using `npx`, so they work even when `npx` is blocked by PowerShell's Execution Policy.
37
24
 
38
- **Already have Node and npm?** You can also install manually without `npx`:
39
-
40
- ```bash
41
- # Global install
42
- npm install -g super-backlog
25
+ # 2 · wire your project (Backlog.md, skills, npm scripts, hooks)
43
26
  sbl init
44
- sbl init --models
45
-
46
- # Local install
47
- npm install super-backlog
48
- node ./node_modules/super-backlog/dist/bin.js init
49
- ```
50
27
 
51
- After installation:
52
-
53
- ```bash
54
- npm run board # open the Backlog.md kanban board
55
- sbl dashboard # live Project Dashboard on http://localhost:6428
28
+ # 3 · open the live Project Dashboard
29
+ sbl dashboard
56
30
  ```
57
31
 
58
- `init` is idempotent safe to re-run any time; re-running with a newer kit version is the upgrade path for all injected files.
59
-
60
- ## What gets installed
32
+ That's it. Everything else below is detail.
61
33
 
62
- | Target | Content | Ownership model |
63
- |---|---|---|
64
- | `package.json` devDependencies | `backlog.md@latest` + `super-backlog@latest` | merged |
65
- | `backlog/` | created by `backlog init --defaults` (upstream owns it) | delegated |
66
- | `opencode.json` | `plugin[] += superpowers@git+https://github.com/obra/superpowers.git` | merged; other keys untouched |
67
- | `.claude/` | Superpowers via official marketplace — init prints the exact command to paste (`/plugin install superpowers@claude-plugins-official`); file-based skills work immediately | instructed/delegated |
68
- | `AGENTS.md` | `<!-- SUPER-BACKLOG:x.y.z START -->` … `<!-- SUPER-BACKLOG END -->` workflow block | marker-scoped |
69
- | `CLAUDE.md` | one-line pointer to the AGENTS.md block | marker-scoped |
70
- | `.opencode/skill/<skill>/SKILL.md` (3 glue skills) | skill templates | fingerprint header line |
71
- | `.claude/skills/<skill>/SKILL.md` | same templates | fingerprint header line |
72
- | `package.json` scripts | `tasks` → `backlog task list`, `board` → `backlog board`, `browser` → `backlog browser`, `dashboard` → `super-backlog dashboard` (never overwrite existing values) | merged, add-only-if-absent |
73
- | `dashboard.html` | generated Project Dashboard | not installed in user projects; generated on demand by `sbl dashboard` |
74
- | `.git/hooks/pre-commit` | integrity guard hook — only with `--guard` (opt-in) | appended marker block |
34
+ ---
75
35
 
76
- Commands: `sbl init` · `sbl models` · `sbl doctor` · `sbl uninstall [--with-backlog]` · `sbl update` · `sbl dashboard [--port <n>] [--no-open]` (alias: `sbl db`). See `sbl help` for every flag.
36
+ ## Command cheat sheet
77
37
 
78
- `sbl update` first self-updates a globally installed CLI to the latest npm version and re-runs itself (opt out with `--no-self` or `SBL_SKIP_UPDATE_CHECK`), then refreshes injected files.
38
+ | Command | What it does |
39
+ | --- | --- |
40
+ | `sbl init` | Wire Backlog.md, Superpowers skills, npm scripts, and hooks into the current project. |
41
+ | `sbl dashboard` | Live Project Dashboard on `http://localhost:6428` (alias: `sbl db`). |
42
+ | `sbl phase TASK-1` | Show where a task stands: `phase/spec → plan → impl → verify`. |
43
+ | `sbl phase TASK-1 plan` | Advance the phase after its gate is passed (`done` clears the label). |
44
+ | `sbl doctor` | Check Node, PowerShell policy, the `backlog` CLI, and phase-label hygiene. |
45
+ | `sbl update` | Self-update the CLI, then refresh every injected file. |
46
+ | `sbl models enable` | Optional model router: cheap models for simple agents, your main model for hard work. |
47
+ | `sbl uninstall` | Remove everything super-backlog owns — your `backlog/` data stays. |
79
48
 
80
- ## Model router (opt-in)
49
+ Full reference with flags and parameters: [Quick start guide](https://adam-s-k-i.github.io/super-backlog/guide/quickstart).
81
50
 
82
- `sbl init --models` adds an optional model router that routes simple work to cheaper model tiers while keeping your main model for complex tasks.
51
+ ---
83
52
 
84
- ```bash
85
- sbl init --models # install router + harness adapters
86
- sbl models enable # turn routing on
87
- sbl models disable # turn routing off
88
- sbl models show # inspect current config
89
- sbl models discover # discover available OpenCode models
90
- ```
53
+ ## Why
91
54
 
92
- When enabled:
55
+ Superpowers defines *how* agents work (brainstorm → plan → TDD → review), Backlog.md defines *what* is tracked (markdown tasks, kanban board). Nothing wired them together — super-backlog is the glue: one `sbl init` installs and maintains everything, with a guaranteed-clean exit (`sbl uninstall`).
93
56
 
94
- - **OpenCode** the plugin `sbl-model-router.js` rewrites `chat.params` for the `sbl-worker` (workhorse) and `sbl-worker-cheap` / `explore` (budget) agents.
95
- - **Claude Code** — agent files carry a `model:` placeholder that is updated by a `SessionStart` hook based on your current main model.
96
- - **Dashboard** — `sbl dashboard` exposes `/api/models` and `/api/models/discover`.
57
+ Tasks carry their pipeline phase as a label (`phase/spec plan → impl → verify`), so every session resumes exactly where work stopped, the dashboard shows live phase counts, and `sbl doctor` guards the convention.
97
58
 
98
- The router is fully owned by super-backlog and removed by `sbl uninstall`. See the [model router design](docs/superpowers/specs/2026-08-26-sbl-model-router-design.md) for details.
59
+ ## What gets installed
99
60
 
100
- ## Pipeline phases
61
+ | Target | Content |
62
+ | --- | --- |
63
+ | `backlog/` | Your Backlog.md data (created by `backlog init --defaults`) |
64
+ | `AGENTS.md` / `CLAUDE.md` | Workflow block with pipeline, gates, and phase rules |
65
+ | `.opencode/skill/` + `.claude/skills/` | Glue skills: `spec-to-backlog`, `task-review-gate`, `backlog-status-report` |
66
+ | `opencode.json` | Superpowers plugin entry |
67
+ | `package.json` scripts | `tasks`, `board`, `browser`, `dashboard` |
68
+ | `.git/hooks/pre-commit` | Integrity guard — opt-in via `--guard` |
101
69
 
102
- Tasks carry their pipeline phase as a label (`phase/spec` `phase/plan` `phase/impl` → `phase/verify`), managed by `sbl phase <task-id> [phase|done]` and checked by `sbl doctor`. The dashboard stepper, task table, and task modal render the live phase. Details: [docs/guide/pipeline-phases.md](docs/guide/pipeline-phases.md).
70
+ `init` is idempotent re-run any time to upgrade injected files.
103
71
 
104
72
  ## Project Dashboard
105
73
 
106
- `sbl dashboard` starts a local hub that serves an HTS-style cockpit (light/dark theme toggle) rendered from your Backlog data in eight sections — Board & Quick Actions, Status (KPI tiles, donut, and an aging strip for open tasks), Feature Cycle (pipeline stepper plus an Up Next / Blocked flow view from task dependencies), Milestones, Drafts (click a card for details), Tasks (sortable/filterable table; click a row to open a modal detail view with acceptance criteria and dependencies), Activity (a 26-week calendar heatmap; click a day for its tasks), and Decisions & Docs. Glossary tooltips explain domain terms inline; extend or override them project-wide via `backlog/docs/glossary.md` (`## Term` heading plus the text below it). Typefaces load from Google Fonts with full system fallbacks — the one external resource; everything else is inline, and the dashboard still renders offline. Bookmark `http://127.0.0.1:6428/p/<project_name>/`. The hub watches `backlog/`, regenerates on change, and serves on port `6428`; connected browser tabs reload automatically via Server-Sent Events. A second repo's `sbl dashboard` attaches to the same hub. `Ctrl+C` in the hub terminal stops all projects.
107
-
108
- ### Keeping it fresh
74
+ ```bash
75
+ sbl dashboard
76
+ ```
109
77
 
110
- Run `sbl dashboard` whenever you want a live view of the board. The hub regenerates the dashboard while it runs; stop it with `Ctrl+C` in the hub terminal. There is no static `dashboard.html` installed in your project.
78
+ A live cockpit rendered from your backlog data: status KPIs, milestones, phase pipeline with live counts, drafts, tasks table (sortable, filterable), 26-week activity heatmap, decisions & docs. Watches `backlog/`, reloads connected tabs automatically, runs on port `6428`.
111
79
 
112
80
  ![Project Dashboard](docs/assets/dashboard.png)
113
81
 
114
- ## Uninstall guarantee
82
+ ## Requirements
83
+
84
+ - Node >= 20 and a package manager (npm, pnpm, or bun)
85
+ - Works with **OpenCode** and **Claude Code**
115
86
 
116
- super-backlog uninstall removes only provably owned artifacts and keeps your Backlog task data unless you pass --with-backlog. Every removal decision is reported line by line as removed / kept / skipped; files whose ownership cannot be proven are left untouched.
87
+ ## Model router (opt-in)
117
88
 
118
- ## Harness support
89
+ `sbl init --models` routes simple agents to cheaper tiers while keeping your main model for complex work. Toggle with `sbl models enable|disable`, inspect with `sbl models show`, discover tiers with `sbl models discover`. [Design doc](docs/superpowers/specs/2026-08-26-sbl-model-router-design.md).
119
90
 
120
- - **OpenCode** — native: `opencode.json` plugin entry plus file-based skills under `.opencode/skill/` (spec-to-backlog, backlog-status-report, task-review-gate).
121
- - **Claude Code** — file-based skills under `.claude/skills/` always work immediately; the marketplace plugin cannot be installed from a script, so init prints the exact command to paste (`/plugin install superpowers@claude-plugins-official`) and exits with a warning (exit code 4).
91
+ ## Uninstall guarantee
122
92
 
123
- Details and matrix: [docs/guide/harness-support.md](docs/guide/harness-support.md).
93
+ `sbl uninstall` removes only provably owned artifacts, reports every decision line by line, and keeps your task data unless you pass `--with-backlog`.
124
94
 
125
- ## Troubleshooting
95
+ ## Docs & troubleshooting
126
96
 
127
- Windows OpenCode fallback, exit codes, and environment seams: [docs/guide/troubleshooting.md](docs/guide/troubleshooting.md). Architecture deep-dive: [docs/guide/architecture.md](docs/guide/architecture.md). Guard hook details: [docs/guide/guard.md](docs/guide/guard.md).
97
+ Full documentation lives on [GitHub Pages](https://adam-s-k-i.github.io/super-backlog/) — quickstart, architecture, pipeline phases, harness support, guard hook, operations. Architecture deep-dive: [docs/guide/architecture.md](docs/guide/architecture.md).
128
98
 
129
99
  ## Development
130
100
 
@@ -8,6 +8,7 @@ import { startHubServer } from '../dashboard/hub.js';
8
8
  import { renderDashboard } from '../dashboard/render.js';
9
9
  import { DASHBOARD_PORT } from '../dashboard/server.js';
10
10
  import { atomicWrite } from '../lib/atomic.js';
11
+ import { defaultBuildFingerprint } from '../lib/build-fingerprint.js';
11
12
  import { clearHubState, isPidAlive, newHubToken, readHubState, writeHubState } from '../lib/hub-state.js';
12
13
  import { projectSlug } from '../lib/slug.js';
13
14
  import { KIT_VERSION } from '../lib/version.js';
@@ -157,6 +158,8 @@ export async function runDashboard(cwd, args, deps = {}) {
157
158
  const isAlive = deps.isAlive ?? isPidAlive;
158
159
  const killPid = deps.killPid ?? ((p) => { process.kill(p); });
159
160
  const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
161
+ const buildFingerprint = deps.buildFingerprint ?? defaultBuildFingerprint;
162
+ const myFingerprint = buildFingerprint();
160
163
  let slugResult;
161
164
  try {
162
165
  slugResult = projectSlug(cwd);
@@ -180,7 +183,14 @@ export async function runDashboard(cwd, args, deps = {}) {
180
183
  : undefined;
181
184
  const liveVersion = typeof statusJson?.version === 'string' ? statusJson.version : undefined;
182
185
  const effectiveVersion = liveVersion ?? state.version;
183
- if (effectiveVersion === KIT_VERSION) {
186
+ const liveFingerprint = typeof statusJson?.fingerprint === 'string' ? statusJson.fingerprint : undefined;
187
+ // Same version AND (proven identical build OR build cannot be
188
+ // assessed locally) -> attach. A missing live fingerprint from a
189
+ // same-version hub means it was started by an older CLI and cannot
190
+ // see the current build, so it is restarted exactly once.
191
+ const buildMatches = myFingerprint !== null && liveFingerprint === myFingerprint;
192
+ const buildUnassessable = myFingerprint === null;
193
+ if (effectiveVersion === KIT_VERSION && (buildMatches || buildUnassessable)) {
184
194
  if (values['port'] !== undefined && port !== state.port) {
185
195
  console.error(`error: a hub is already running on ${state.port}`);
186
196
  return 1;
@@ -194,7 +204,12 @@ export async function runDashboard(cwd, args, deps = {}) {
194
204
  noOpen,
195
205
  });
196
206
  }
197
- console.error(`hub v${effectiveVersion ?? 'unknown'} does not match this CLI (v${KIT_VERSION}) — restarting it`);
207
+ if (effectiveVersion === KIT_VERSION) {
208
+ console.error(`hub build changed (fingerprint mismatch, same version v${KIT_VERSION}) — restarting it`);
209
+ }
210
+ else {
211
+ console.error(`hub v${effectiveVersion ?? 'unknown'} does not match this CLI (v${KIT_VERSION}) — restarting it`);
212
+ }
198
213
  killPid(state.pid);
199
214
  let dead = !isAlive(state.pid);
200
215
  for (let attempt = 0; !dead && attempt < STOP_POLL_MAX_ATTEMPTS; attempt++) {
@@ -234,7 +249,7 @@ export async function runDashboard(cwd, args, deps = {}) {
234
249
  console.error(`error: dashboard serve failed (${err instanceof Error ? err.message : String(err)})`);
235
250
  return 1;
236
251
  }
237
- writeHubState(home, { pid, port: hub.port, token, version: KIT_VERSION });
252
+ writeHubState(home, { pid, port: hub.port, token, version: KIT_VERSION, fingerprint: myFingerprint ?? undefined });
238
253
  const result = hub.register({ cwd, file: outPath, regenerate });
239
254
  if (!result.ok) {
240
255
  await hub.close();
@@ -9,6 +9,7 @@ import { collectDashboardData } from './data.js';
9
9
  import { renderDashboard } from './render.js';
10
10
  import { createDebouncedReloader, createReloadBroker, DASHBOARD_PORT, recursiveWatchSupported, } from './server.js';
11
11
  import { atomicWrite } from '../lib/atomic.js';
12
+ import { defaultBuildFingerprint } from '../lib/build-fingerprint.js';
12
13
  import { projectSlug, realpathKey } from '../lib/slug.js';
13
14
  import { KIT_VERSION } from '../lib/version.js';
14
15
  import { createModelApiHandler } from '../models/dashboard-api.js';
@@ -63,6 +64,7 @@ function generateDashboard(cwd, file) {
63
64
  export async function startHubServer(opts) {
64
65
  const projects = new Map();
65
66
  const token = opts.token;
67
+ const fingerprint = defaultBuildFingerprint();
66
68
  let port = 0;
67
69
  let watchWarned = false;
68
70
  function watchBacklog(cwd, reloader) {
@@ -166,7 +168,7 @@ export async function startHubServer(opts) {
166
168
  sendText(res, 401, 'unauthorized');
167
169
  return;
168
170
  }
169
- sendJson(res, 200, { pid: process.pid, port, version: KIT_VERSION });
171
+ sendJson(res, 200, { pid: process.pid, port, version: KIT_VERSION, fingerprint: fingerprint ?? undefined });
170
172
  return;
171
173
  }
172
174
  if (pathname === '/api/hub/register' && method === 'POST') {
@@ -0,0 +1,50 @@
1
+ // src/lib/build-fingerprint.ts
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { dirname, join } from 'node:path';
6
+ const require = createRequire(import.meta.url);
7
+ function distRoot() {
8
+ // Resolves from src/lib (dev/tests) and dist/lib (runtime) alike: both sit
9
+ // two levels below the package root that owns dist/.
10
+ const pkgPath = require.resolve('../../package.json');
11
+ return join(dirname(pkgPath), 'dist');
12
+ }
13
+ function walk(dir, out) {
14
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
15
+ const full = join(dir, entry.name);
16
+ if (entry.isDirectory())
17
+ walk(full, out);
18
+ else
19
+ out.push(full);
20
+ }
21
+ }
22
+ /**
23
+ * Stable content hash over the built CLI. Two builds of the same source
24
+ * produce the same fingerprint; any changed or added file changes it.
25
+ * Returns null when the build directory is missing.
26
+ */
27
+ export function computeBuildFingerprint(root = distRoot()) {
28
+ if (!existsSync(root))
29
+ return null;
30
+ const files = [];
31
+ walk(root, files);
32
+ if (files.length === 0)
33
+ return null;
34
+ files.sort();
35
+ const hash = createHash('sha256');
36
+ for (const file of files) {
37
+ hash.update(file.slice(root.length + 1).replaceAll('\\', '/'));
38
+ hash.update('\0');
39
+ hash.update(readFileSync(file));
40
+ hash.update('\0');
41
+ }
42
+ return hash.digest('hex').slice(0, 16);
43
+ }
44
+ let cachedDefault;
45
+ /** Process-lifetime cached fingerprint of the running build. */
46
+ export function defaultBuildFingerprint() {
47
+ if (cachedDefault === undefined)
48
+ cachedDefault = computeBuildFingerprint();
49
+ return cachedDefault;
50
+ }
@@ -16,8 +16,14 @@ export function readHubState(home) {
16
16
  typeof parsed.token !== 'string') {
17
17
  return null;
18
18
  }
19
- const { pid, port, token, version } = parsed;
20
- return typeof version === 'string' ? { pid, port, token, version } : { pid, port, token };
19
+ const { pid, port, token, version, fingerprint } = parsed;
20
+ return {
21
+ pid,
22
+ port,
23
+ token,
24
+ ...(typeof version === 'string' ? { version } : {}),
25
+ ...(typeof fingerprint === 'string' ? { fingerprint } : {}),
26
+ };
21
27
  }
22
28
  catch {
23
29
  return null;
@@ -197,11 +197,11 @@
197
197
  #backlog-close:hover { color: var(--text); border-color: var(--line); }
198
198
  #backlog-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
199
199
  #backlog-frame { flex: 1; width: 100%; border: 0; background: var(--bg); }
200
- .dialog-content { padding: 0 24px 26px; }
200
+ .dialog-content { padding: 6px 32px 32px; }
201
201
  .detail-head {
202
202
  position: sticky; top: 0; z-index: 1;
203
203
  display: flex; align-items: center; gap: 10px;
204
- margin: 0 -24px 12px; padding: 16px 24px 12px;
204
+ margin: 0 -32px 12px; padding: 16px 32px 12px;
205
205
  background: var(--surface); border-bottom: 1px solid var(--line);
206
206
  }
207
207
  .detail-id { font-family: var(--mono); color: var(--accent); font-weight: 700; }
@@ -214,6 +214,10 @@
214
214
  .detail-close:hover { color: var(--danger); }
215
215
  .detail-title { font-size: 1.3rem; font-weight: 700; line-height: 1.3; margin-bottom: 10px; overflow-wrap: anywhere; }
216
216
  #task-dialog .detail-desc { color: var(--muted); line-height: 1.6; margin-bottom: 10px; white-space: normal; }
217
+ /* the global reset strips ul padding: without this, list markers poke
218
+ out of the dialog's content box on the left */
219
+ #task-dialog ul { list-style-position: inside; padding-left: 1.15rem; }
220
+ #task-dialog .ac-list { list-style: none; padding-left: 0; }
217
221
  #task-dialog h4 { color: var(--dim); font-size: .7rem; letter-spacing: 1.3px; text-transform: uppercase; margin: 18px 0 8px; }
218
222
  .detail-meta {
219
223
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
@@ -282,11 +286,15 @@
282
286
  }
283
287
 
284
288
  /* ---------- Tasks table ---------- */
285
- .toolbar { display: flex; justify-content: flex-end; margin-bottom: var(--sp, 12px); }
286
- input[type="search"] {
289
+ .toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 10px; margin-bottom: var(--sp, 12px); }
290
+ .status-grid { display: grid; grid-template-columns: minmax(240px, 360px) 1fr; gap: 20px; align-items: start; }
291
+ @media (max-width: 900px) { .status-grid { grid-template-columns: 1fr; } }
292
+ input[type="search"], #tasksize {
287
293
  background: var(--surface-2); color: var(--text); border: 1px solid var(--line-strong);
288
294
  border-radius: 999px; padding: 7px 16px; min-width: 260px; font: inherit;
289
295
  }
296
+ #tasksize { min-width: 0; padding: 7px 12px; cursor: pointer; }
297
+ #tasksize:focus { outline: none; border-color: var(--accent-dim); box-shadow: 0 0 0 2px var(--focus-ring); }
290
298
  input[type="search"]:focus { outline: none; border-color: var(--accent-dim); box-shadow: 0 0 0 2px var(--focus-ring); }
291
299
  .table-wrap { overflow-x: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 12px; }
292
300
  table { width: 100%; border-collapse: collapse; font-size: .89rem; }
@@ -516,11 +524,10 @@
516
524
  <a href="#sec-01"><span class="n">01</span>Board &amp; Quick Actions</a>
517
525
  <a href="#sec-02"><span class="n">02</span>Status</a>
518
526
  <a href="#sec-03"><span class="n">03</span>Feature Cycle</a>
519
- <a href="#sec-04"><span class="n">04</span>Milestones</a>
520
- <a href="#sec-05"><span class="n">05</span>Drafts</a>
521
- <a href="#sec-06"><span class="n">06</span>Tasks</a>
522
- <a href="#sec-07"><span class="n">07</span>Activity</a>
523
- <a href="#sec-08"><span class="n">08</span>Decisions &amp; Docs</a>
527
+ <a href="#sec-04"><span class="n">04</span>Drafts</a>
528
+ <a href="#sec-05"><span class="n">05</span>Tasks</a>
529
+ <a href="#sec-06"><span class="n">06</span>Activity</a>
530
+ <a href="#sec-07"><span class="n">07</span>Decisions &amp; Docs</a>
524
531
  </nav>
525
532
  <div class="legend">
526
533
  <h4>Status</h4>
@@ -546,7 +553,13 @@
546
553
  <section id="sec-02">
547
554
  <div class="sec-head"><span class="sec-num">02</span><h2>Status</h2><span class="tagline">progress &middot; velocity &middot; wip &middot; age</span></div>
548
555
  <div id="kpis" class="mount"></div>
549
- <div id="donut" class="mount"></div>
556
+ <div class="status-grid">
557
+ <div id="donut" class="mount"></div>
558
+ <div class="bars-col">
559
+ <h3 class="sub-head"><span class="term" data-term="Milestone">Milestone</span> &mdash; done / total</h3>
560
+ <div id="bars" class="mount"></div>
561
+ </div>
562
+ </div>
550
563
  <h3 class="sub-head">Aging &mdash; open tasks by days in the backlog</h3>
551
564
  <div id="aging" class="mount"></div>
552
565
  </section>
@@ -560,22 +573,24 @@
560
573
  </section>
561
574
 
562
575
  <section id="sec-04">
563
- <div class="sec-head"><span class="sec-num">04</span><h2>Milestones</h2><span class="tagline">done / total per <span class="term" data-term="Milestone">Milestone</span></span></div>
564
- <div id="bars" class="mount"></div>
565
- </section>
566
-
567
- <section id="sec-05">
568
- <div class="sec-head"><span class="sec-num">05</span><h2>Drafts</h2><span class="tagline">ideas before they enter the backlog &middot; click a card for details</span></div>
576
+ <div class="sec-head"><span class="sec-num">04</span><h2>Drafts</h2><span class="tagline">ideas before they enter the backlog &middot; click a card for details</span></div>
569
577
  <div id="drafts" class="mount">
570
578
  <ul id="drafts-list" class="drafts-list"></ul>
571
579
  </div>
572
580
  </section>
573
581
 
574
- <section id="sec-06">
575
- <div class="sec-head"><span class="sec-num">06</span><h2>Tasks</h2><span class="tagline">sort &middot; filter &middot; click row for details</span></div>
582
+ <section id="sec-05">
583
+ <div class="sec-head"><span class="sec-num">05</span><h2>Tasks</h2><span class="tagline">sort &middot; filter &middot; click row for details</span></div>
576
584
  <div id="tasks" class="mount">
577
585
  <div class="toolbar">
578
586
  <input id="taskfilter" type="search" placeholder="Filter tasks&hellip;" aria-label="Filter tasks">
587
+ <select id="tasksize" aria-label="Rows to show">
588
+ <option value="10">10</option>
589
+ <option value="20" selected>20</option>
590
+ <option value="50">50</option>
591
+ <option value="100">100</option>
592
+ <option value="all">all</option>
593
+ </select>
579
594
  </div>
580
595
  <div class="table-wrap">
581
596
  <table id="tasks-table">
@@ -596,15 +611,15 @@
596
611
  </div>
597
612
  </section>
598
613
 
599
- <section id="sec-07">
600
- <div class="sec-head"><span class="sec-num">07</span><h2>Activity</h2><span class="tagline">last 26 weeks &middot; click a day for its tasks</span></div>
614
+ <section id="sec-06">
615
+ <div class="sec-head"><span class="sec-num">06</span><h2>Activity</h2><span class="tagline">last 26 weeks &middot; click a day for its tasks</span></div>
601
616
  <div id="activity-kpis" class="mount"></div>
602
617
  <div id="heatmap" class="mount"></div>
603
618
  <div id="day-panel" class="flow-block" hidden></div>
604
619
  </section>
605
620
 
606
- <section id="sec-08">
607
- <div class="sec-head"><span class="sec-num">08</span><h2>Decisions &amp; Docs</h2><span class="tagline">decisions &middot; docs &middot; glossary</span></div>
621
+ <section id="sec-07">
622
+ <div class="sec-head"><span class="sec-num">07</span><h2>Decisions &amp; Docs</h2><span class="tagline">decisions &middot; docs &middot; glossary</span></div>
608
623
  <div id="docs" class="mount"></div>
609
624
  <p class="hint">The <span class="term" data-term="TDD">TDD</span> loop and the <span class="term" data-term="DoD">DoD</span> ship as built-in glossary terms &mdash; extend them via <code class="inline">backlog/docs/glossary.md</code>.</p>
610
625
  </section>
@@ -671,7 +686,7 @@
671
686
  }
672
687
 
673
688
  var KEYS = ['id', 'title', 'status', 'milestone', 'priority', 'assignee', 'updated'];
674
- var state = { key: 'id', dir: 1, query: '', status: null, hoverStatus: null, special: null };
689
+ var state = { key: 'updated', dir: -1, size: 20, query: '', status: null, hoverStatus: null, special: null };
675
690
  function field(task, key) {
676
691
  var v = task[key];
677
692
  return v === undefined || v === null ? '' : String(v);
@@ -754,8 +769,9 @@
754
769
  var rows = data.tasks
755
770
  .filter(matches)
756
771
  .sort(function (a, b) { return compareTasks(a, b, state.key) * state.dir; });
772
+ var shown = state.size === 'all' ? rows : rows.slice(0, state.size);
757
773
  tbody.textContent = '';
758
- if (rows.length === 0) {
774
+ if (shown.length === 0) {
759
775
  var emptyTr = el('tr');
760
776
  var emptyTd = el('td', '', 'No matching tasks.');
761
777
  emptyTd.colSpan = KEYS.length;
@@ -763,7 +779,7 @@
763
779
  tbody.appendChild(emptyTr);
764
780
  return;
765
781
  }
766
- rows.forEach(function (task) {
782
+ shown.forEach(function (task) {
767
783
  var tr = el('tr', 'task-row');
768
784
  tr.setAttribute('data-task', task.id);
769
785
  KEYS.forEach(function (k) {
@@ -811,6 +827,18 @@
811
827
  renderTasks();
812
828
  });
813
829
  });
830
+ var initialTh = document.querySelector('#tasks-table th[data-key="updated"]');
831
+ if (initialTh) {
832
+ initialTh.classList.add('sorted-desc');
833
+ initialTh.setAttribute('aria-sort', 'descending');
834
+ }
835
+ var sizeSel = document.getElementById('tasksize');
836
+ if (sizeSel) {
837
+ sizeSel.addEventListener('change', function () {
838
+ state.size = this.value === 'all' ? 'all' : parseInt(this.value, 10);
839
+ renderTasks();
840
+ });
841
+ }
814
842
  var filterInput = document.getElementById('taskfilter');
815
843
  if (filterInput) {
816
844
  filterInput.addEventListener('input', function (event) {
@@ -868,6 +896,14 @@
868
896
  }
869
897
  if (backlogBtn) {
870
898
  backlogBtn.addEventListener('click', function () {
899
+ var hosted = location.hostname !== '127.0.0.1' && location.hostname !== 'localhost';
900
+ if (hosted) {
901
+ /* static preview (e.g. GitHub Pages): no hub, no browser process */
902
+ cmdFeedback(backlogBtn, 'needs local hub');
903
+ backlogBtn.setAttribute('data-tip', 'Static preview: this button opens the Backlog.md UI only under sbl dashboard. Run sbl dashboard in your project and open http://localhost:6428');
904
+ backlogBtn.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
905
+ return;
906
+ }
871
907
  cmdFeedback(backlogBtn, 'starting\u2026');
872
908
  fetch('api/backlog-browser', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
873
909
  .then(function (res) { return res.json().then(function (data) { return { ok: res.ok, data: data }; }); })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-backlog",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "One command to equip any project with Backlog.md + Superpowers, plus a Project Dashboard.",
5
5
  "license": "MIT",
6
6
  "repository": {