@nanocollective/roster 0.1.0-alpha.4 → 0.1.0-alpha.40

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.
Files changed (97) hide show
  1. package/README.md +70 -84
  2. package/dist/cli.js +4562 -2744
  3. package/docs/README.md +9 -6
  4. package/docs/agents.md +24 -20
  5. package/docs/architecture.md +13 -5
  6. package/docs/charters/analyst.md +65 -0
  7. package/docs/charters/cmo.md +69 -0
  8. package/docs/charters/community.md +63 -0
  9. package/docs/charters/cto.md +71 -0
  10. package/docs/charters/designer.md +65 -0
  11. package/docs/charters/devops.md +65 -0
  12. package/docs/charters/pm.md +70 -0
  13. package/docs/charters/qa.md +65 -0
  14. package/docs/charters/support.md +60 -0
  15. package/docs/charters/writer.md +63 -0
  16. package/docs/commands.md +90 -5
  17. package/docs/concepts.md +48 -14
  18. package/docs/cost.md +36 -1
  19. package/docs/developing.md +16 -21
  20. package/docs/doctor-codes.md +8 -2
  21. package/docs/export.md +2 -0
  22. package/docs/extending.md +2 -2
  23. package/docs/getting-started.md +126 -77
  24. package/docs/images/brain.jpg +0 -0
  25. package/docs/images/org.jpg +0 -0
  26. package/docs/images/prompt.jpg +0 -0
  27. package/docs/images/setup-org.jpg +0 -0
  28. package/docs/images/setup-plan.jpg +0 -0
  29. package/docs/images/staff.jpg +0 -0
  30. package/docs/manual-steps.md +94 -123
  31. package/docs/memory.md +21 -3
  32. package/docs/org-yaml.md +39 -2
  33. package/docs/portal.md +119 -44
  34. package/docs/prompts.md +31 -4
  35. package/docs/security.md +37 -5
  36. package/docs/session-workflow.md +49 -17
  37. package/docs/staff-yaml.md +14 -2
  38. package/docs/troubleshooting.md +8 -8
  39. package/docs/upgrading.md +9 -3
  40. package/docs/writing-a-charter.md +28 -0
  41. package/package.json +1 -1
  42. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +6 -0
  43. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +6 -0
  44. package/templates/brain/CHARTER.md +3 -3
  45. package/templates/brain/README.md +1 -0
  46. package/templates/brain/log/decisions.md +3 -0
  47. package/templates/brain/strategy/ideas.md +7 -0
  48. package/templates/briefs/priorities.md +46 -0
  49. package/templates/ops/.github/workflows/session.yaml +108 -14
  50. package/templates/ops/agents.mjs +7 -3
  51. package/templates/ops/compose.mjs +16 -3
  52. package/templates/ops/inflight.mjs +157 -0
  53. package/templates/ops/org/operating.md +21 -1
  54. package/templates/ops/org/voice.md +9 -0
  55. package/templates/ops/prompts/_inflight.md +14 -0
  56. package/templates/ops/prompts/_paths.md +2 -1
  57. package/templates/ops/prompts/daily.md +16 -7
  58. package/templates/ops/prompts/mention.md +2 -0
  59. package/templates/ops/run-record.mjs +144 -0
  60. package/templates/portal/css/base.css +146 -73
  61. package/templates/portal/css/brain.css +23 -20
  62. package/templates/portal/css/diff.css +10 -9
  63. package/templates/portal/css/graph.css +12 -7
  64. package/templates/portal/css/health.css +13 -11
  65. package/templates/portal/css/inbox.css +57 -25
  66. package/templates/portal/css/layout.css +96 -41
  67. package/templates/portal/css/markdown.css +36 -14
  68. package/templates/portal/css/runs.css +13 -0
  69. package/templates/portal/css/setup.css +116 -34
  70. package/templates/portal/index.html +18 -2
  71. package/templates/portal/js/api.js +41 -4
  72. package/templates/portal/js/app.js +132 -9
  73. package/templates/portal/js/dialog.js +82 -0
  74. package/templates/portal/js/icons.js +37 -0
  75. package/templates/portal/js/inflight.js +18 -0
  76. package/templates/portal/js/md.js +5 -2
  77. package/templates/portal/js/mdedit.js +84 -0
  78. package/templates/portal/js/readiness.js +35 -0
  79. package/templates/portal/js/refresh.js +2 -1
  80. package/templates/portal/js/state.js +17 -4
  81. package/templates/portal/js/views/app.js +24 -7
  82. package/templates/portal/js/views/checklist.js +10 -4
  83. package/templates/portal/js/views/credential.js +84 -0
  84. package/templates/portal/js/views/graph.js +1 -1
  85. package/templates/portal/js/views/health.js +17 -4
  86. package/templates/portal/js/views/hire.js +583 -0
  87. package/templates/portal/js/views/inbox.js +248 -81
  88. package/templates/portal/js/views/org.js +46 -106
  89. package/templates/portal/js/views/orgedit.js +234 -0
  90. package/templates/portal/js/views/paste.js +87 -21
  91. package/templates/portal/js/views/prompt.js +11 -4
  92. package/templates/portal/js/views/repos.js +20 -15
  93. package/templates/portal/js/views/runonce.js +94 -0
  94. package/templates/portal/js/views/runs.js +170 -0
  95. package/templates/portal/js/views/setup.js +257 -75
  96. package/templates/portal/js/views/staff.js +158 -243
  97. package/templates/portal/js/views/todo.js +62 -0
@@ -33,11 +33,15 @@ your brain. Reconstitute yourself, do a day's work, hand off.
33
33
  **Do not read `log/decisions.md` at boot**; it is the audit trail, for when you need to know why
34
34
  something was decided.
35
35
 
36
+ {{> prompts/_inflight.md}}
37
+ {{>? org/priorities.md}}
38
+
36
39
  ## Then work. Autonomously.
37
40
 
38
- Take the top item off #{{staff.status_issue}}'s ordered list, unless something above changed the
39
- priority, in which case say so in your report and do the more urgent thing. **Then actually do it.**
40
- You are not writing a plan for {{human.name}} to approve.
41
+ Take the top item off #{{staff.status_issue}}'s ordered list that serves the org's priorities
42
+ (`org/priorities.md`, where there is one), unless something above changed the priority, in which
43
+ case say so in your report and do the more urgent thing. **Then actually do it.** You are not
44
+ writing a plan for {{human.name}} to approve.
41
45
 
42
46
  {{> org/operating.md}}
43
47
 
@@ -48,15 +52,16 @@ You are not writing a plan for {{human.name}} to approve.
48
52
  {{#if staff.product}}
49
53
  1. **Open the PR** on `{{staff.product.repo}}` if you produced anything there, from a branch:
50
54
  `GH_TOKEN=${{staff.public_token_env}} gh pr create --repo {{staff.product.repo}} ...`
51
- **Body: what it does, what the gate covered, what it did not cover. Nothing else** - no design
52
- essay, no narration of how you built it. It is reviewed on a phone and the diff is right there.
55
+ **Body: which priority it serves, what it does, what the gate covered, what it did not cover.
56
+ Nothing else** - no design essay, no narration of how you built it. It is reviewed on a phone
57
+ and the diff is right there.
53
58
  {{/if}}
54
59
  2. **Rewrite pinned issue #{{staff.status_issue}} "Where we are"**: the situation in a line, what
55
60
  this run did, what the next run picks up in priority order. **It is a handover for the next run,
56
61
  not a diary.** Rewrite it, do not append, and cut anything the next run can find for itself.
57
62
  3. **Reconcile the tracker.** Open issues for anything new needing {{human.name}}, labelled by owner
58
- plus kind, assigned to `{{human.github}}`. Close what genuinely completed, citing evidence.
59
- **Never close a `decision` issue.** **Comments and replies get the same concision as everything
63
+ plus kind, assigned to `{{human.github}}`. Sweep every open issue you opened: close what is done
64
+ or superseded, citing what closed it. **Comments and replies get the same concision as everything
60
65
  else:** what changed and what it means for them. A comment that only says an issue is still open
61
66
  is not worth the notification.
62
67
  4. **Update `{{staff.dir}}/memory/` only if a fact or watch-out changed.** A new fact is **one line**
@@ -64,6 +69,10 @@ You are not writing a plan for {{human.name}} to approve.
64
69
  with "updated:". If it needs an argument, that goes in `memory/notes/<slug>.md` and the line stays
65
70
  one line. **Delete any line that no longer changes a decision** and say so in the decision log.
66
71
  If the run was purely work, touch nothing.
72
+ **Then check the budget:** `wc -c {{staff.dir}}/memory/INDEX.md {{staff.dir}}/log/decisions.md`.
73
+ Over 24KB either, or any fact over 400 characters (unless a `memory:` block in `staff.yaml` or
74
+ `org.yaml` sets other limits), and pruning is this run's job: delete, shorten, move arguments
75
+ to notes, and roll older decisions into `log/decisions/<YYYY-MM>.md`.
67
76
  5. **Log real decisions** in `{{staff.dir}}/log/decisions.md`, dated, newest at top, with the why.
68
77
  {{#if peers}}
69
78
  6. **Write to the other staff** if anything shipped, changed or broke that touches their patch.
@@ -35,6 +35,8 @@ An issue opened this way often carries a pull request somewhere else, and says w
35
35
 
36
36
  `gh issue view --comments` is broken; use `gh api` as above.
37
37
 
38
+ {{> prompts/_inflight.md}}
39
+
38
40
  ## Do the work
39
41
 
40
42
  - **Read `{{staff.dir}}/CHARTER.md` and `{{staff.dir}}/memory/INDEX.md` before acting.** They are
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ // Writes down what one run was and what it cost, after the agent has finished or failed.
3
+ //
4
+ // Vendored alongside compose.mjs for the same reason: it runs on the runner, and must not depend
5
+ // on npm or on an org the tenant does not control.
6
+ //
7
+ // The record is small and deliberately incomplete. Staff, kind, outcome and duration are always
8
+ // known. Turns, cost and tokens are known only when the agent says so: claude-code-action writes
9
+ // an execution file, the `claude` CLI writes JSON when asked, and anything else may say nothing.
10
+ // An unknown is null, never a guess, because a total built from guesses is worse than none.
11
+ //
12
+ // Usage: node roster-ops/run-record.mjs --out .roster-run/run.json
13
+ // (everything else arrives in the environment; see session.yaml)
14
+
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
16
+ import { dirname, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ /**
20
+ * The agent's own account of the run, from whatever it wrote.
21
+ *
22
+ * Three shapes turn up: an array of messages whose last `result` is the summary (the Action's
23
+ * execution file), a single result object (`claude -p --output-format json`), and one message
24
+ * per line (`stream-json`). All three end in the same object, so find that.
25
+ */
26
+ export function readResult(text) {
27
+ if (!text || !text.trim()) return null;
28
+ let items;
29
+ try {
30
+ const parsed = JSON.parse(text);
31
+ items = Array.isArray(parsed) ? parsed : [parsed];
32
+ } catch {
33
+ items = [];
34
+ for (const line of text.split("\n")) {
35
+ try {
36
+ items.push(JSON.parse(line));
37
+ } catch {
38
+ // A line of log output between the JSON is not ours to fail on.
39
+ }
40
+ }
41
+ }
42
+ const results = items.filter(
43
+ (m) => m && typeof m === "object" && (m.type === "result" || "total_cost_usd" in m),
44
+ );
45
+ const last = results[results.length - 1];
46
+ if (!last) return null;
47
+
48
+ const n = (v) => (typeof v === "number" && Number.isFinite(v) ? v : null);
49
+ const u = last.usage ?? {};
50
+ const tokens = {
51
+ input: n(u.input_tokens),
52
+ output: n(u.output_tokens),
53
+ cache_read: n(u.cache_read_input_tokens),
54
+ cache_write: n(u.cache_creation_input_tokens),
55
+ };
56
+ return {
57
+ turns: n(last.num_turns),
58
+ cost_usd: n(last.total_cost_usd ?? last.cost_usd),
59
+ tokens: Object.values(tokens).some((v) => v !== null) ? tokens : null,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * What happened, in one word.
65
+ *
66
+ * The agent step's own outcome when it ran. When it never ran, the job's status says whether
67
+ * that was a cancel (a timeout is one) or a failure somewhere in the setup before it.
68
+ */
69
+ export function outcomeOf(agentOutcome, jobStatus) {
70
+ if (["success", "failure", "cancelled"].includes(agentOutcome)) return agentOutcome;
71
+ if (jobStatus === "cancelled") return "cancelled";
72
+ return "setup-failure";
73
+ }
74
+
75
+ export function buildRecord(env, resultText, now = Date.now()) {
76
+ const started = Number(env.ROSTER_STARTED) || null;
77
+ const result = readResult(resultText);
78
+ return {
79
+ v: 1,
80
+ staff: env.STAFF ?? "",
81
+ kind: env.KIND ?? "",
82
+ outcome: outcomeOf(env.AGENT_OUTCOME, env.JOB_STATUS),
83
+ started: started ? new Date(started * 1000).toISOString() : null,
84
+ duration_s: started ? Math.max(0, Math.round(now / 1000 - started)) : null,
85
+ agent: env.AGENT_ID || null,
86
+ model: env.MODEL || null,
87
+ turns: result?.turns ?? null,
88
+ cost_usd: result?.cost_usd ?? null,
89
+ tokens: result?.tokens ?? null,
90
+ run_id: env.GITHUB_RUN_ID ?? null,
91
+ run_url:
92
+ env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID
93
+ ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`
94
+ : null,
95
+ };
96
+ }
97
+
98
+ /** The same record as the job summary shows it: one table, unknowns as a dash. */
99
+ export function summary(record) {
100
+ const dash = (v) => (v === null || v === undefined ? "—" : String(v));
101
+ const mins = record.duration_s === null ? null : `${Math.round(record.duration_s / 60)}m`;
102
+ const cost = record.cost_usd === null ? null : `$${record.cost_usd.toFixed(2)}`;
103
+ const t = record.tokens;
104
+ const tokens = t
105
+ ? [
106
+ t.input !== null ? `${t.input} in` : "",
107
+ t.output !== null ? `${t.output} out` : "",
108
+ t.cache_read !== null ? `${t.cache_read} cached` : "",
109
+ ]
110
+ .filter(Boolean)
111
+ .join(", ")
112
+ : null;
113
+ return [
114
+ `### ${record.staff} · ${record.kind} · ${record.outcome}`,
115
+ "",
116
+ "| duration | turns | cost | tokens | agent |",
117
+ "|---|---|---|---|---|",
118
+ `| ${dash(mins)} | ${dash(record.turns)} | ${dash(cost)} | ${dash(tokens)} | ${dash(record.agent)} |`,
119
+ "",
120
+ ].join("\n");
121
+ }
122
+
123
+ function main(argv) {
124
+ const args = {};
125
+ for (let i = 0; i < argv.length; i += 2) args[argv[i].replace(/^--/, "")] = argv[i + 1];
126
+ const out = resolve(args.out ?? ".roster-run/run.json");
127
+ const file = process.env.RESULT_FILE;
128
+ const text = file && existsSync(file) ? readFileSync(file, "utf8") : "";
129
+ const record = buildRecord(process.env, text);
130
+
131
+ mkdirSync(dirname(out), { recursive: true });
132
+ writeFileSync(out, JSON.stringify(record, null, 2) + "\n");
133
+ if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary(record));
134
+ process.stdout.write(JSON.stringify(record) + "\n");
135
+ }
136
+
137
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
138
+ try {
139
+ main(process.argv.slice(2));
140
+ } catch (err) {
141
+ console.error(`run-record: ${err.message}`);
142
+ process.exit(1);
143
+ }
144
+ }
@@ -1,30 +1,64 @@
1
1
  /* Tokens, reset, and the primitives every screen uses: buttons, inputs, chips, meta text. */
2
2
 
3
3
  :root{
4
- --bg:#0e1013; --panel:#15181d; --panel-2:#1b1f26; --line:#252a33;
5
- --ink:#e6e9ef; --ink-dim:#9aa3b2; --ink-faint:#6b7484;
6
- --accent:#7cc7a4; --accent-dim:#3d6b56;
7
- --warn:#d8a657; --err:#e07b7b;
8
- --ruled:#7aa2f7; --measured:#7cc7a4; --derived:#c99bd6;
9
- --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
10
- --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
4
+ /* iOS system colours, shared with the website, so the portal and the page that introduces
5
+ it are one product. The older names below are aliases, kept so every screen's stylesheet
6
+ reads the same tokens it always has. `--accent` is for text and lines; `--accent-fill`
7
+ is for solid fills, which on a light background want the brighter green. */
8
+ --bg:#000000; --bg-elev:#1c1c1e; --surface:#1c1c1e; --surface-2:#2c2c2e;
9
+ --border:rgba(255,255,255,.10); --border-strong:rgba(255,255,255,.16);
10
+ --text:#f5f5f7; --text-muted:#98989d; --text-faint:#636366;
11
+ --accent:#30d158; --accent-fill:#30d158; --accent-strong:#30d158;
12
+ --accent-soft:rgba(48,209,88,.15); --accent-ink:#000000; --accent-dim:rgba(48,209,88,.45);
13
+ --blue:#0a84ff; --amber:#ff9f0a; --red:#ff453a; --violet:#bf5af2;
14
+ --side:#161618; --sel:rgba(255,255,255,.10);
15
+ --hover:rgba(255,255,255,.05); --active:rgba(255,255,255,.10);
16
+ --shadow-sm:none; --shadow:none;
17
+ --shadow-lg:0 20px 48px rgba(0,0,0,.6);
18
+ --ease:cubic-bezier(.2,.8,.2,1);
19
+ --r-sm:6px; --r:10px; --r-lg:14px;
20
+ --scroll:rgba(255,255,255,.18); --scroll-hover:rgba(255,255,255,.3);
21
+ --mono:"SF Mono",ui-monospace,"JetBrains Mono",Menlo,monospace;
22
+ --sans:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",system-ui,sans-serif;
23
+
24
+ --panel:var(--surface); --panel-2:var(--surface-2); --line:var(--border);
25
+ --ink:var(--text); --ink-dim:var(--text-muted); --ink-faint:var(--text-faint);
26
+ --warn:var(--amber); --err:var(--red);
27
+ --ruled:var(--blue); --measured:var(--accent); --derived:var(--violet);
28
+ color-scheme:dark;
11
29
  }
12
30
  /* System preference wins unless the toggle has stamped data-theme on the root. */
13
31
  @media (prefers-color-scheme:light){
14
32
  :root:not([data-theme="dark"]){
15
- --bg:#fbfbfa; --panel:#fff; --panel-2:#f4f5f7; --line:#e3e5ea;
16
- --ink:#1b1f26; --ink-dim:#5b6472; --ink-faint:#8b95a5;
17
- --accent:#2f7d5b; --accent-dim:#a8d5bf;
18
- --warn:#a5701d; --err:#b34a4a;
19
- --ruled:#3b5fa8; --measured:#2f7d5b; --derived:#8a52a8;
33
+ --bg:#f5f5f7; --bg-elev:#ffffff; --surface:#ffffff; --surface-2:#f2f2f7;
34
+ --border:rgba(0,0,0,.08); --border-strong:rgba(0,0,0,.14);
35
+ --text:#1d1d1f; --text-muted:#6e6e73; --text-faint:#aeaeb2;
36
+ --accent:#248a3d; --accent-fill:#34c759; --accent-strong:#248a3d;
37
+ --accent-soft:rgba(52,199,89,.12); --accent-ink:#ffffff; --accent-dim:rgba(52,199,89,.45);
38
+ --blue:#007aff; --amber:#c77700; --red:#ff3b30; --violet:#af52de;
39
+ --side:#ebebf0; --sel:rgba(0,0,0,.07);
40
+ --hover:rgba(0,0,0,.04); --active:rgba(0,0,0,.07);
41
+ --shadow-sm:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.04);
42
+ --shadow:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.05);
43
+ --shadow-lg:0 4px 12px rgba(0,0,0,.06),0 20px 48px rgba(0,0,0,.12);
44
+ --scroll:rgba(0,0,0,.18); --scroll-hover:rgba(0,0,0,.3);
45
+ color-scheme:light;
20
46
  }
21
47
  }
22
48
  :root[data-theme="light"]{
23
- --bg:#fbfbfa; --panel:#fff; --panel-2:#f4f5f7; --line:#e3e5ea;
24
- --ink:#1b1f26; --ink-dim:#5b6472; --ink-faint:#8b95a5;
25
- --accent:#2f7d5b; --accent-dim:#a8d5bf;
26
- --warn:#a5701d; --err:#b34a4a;
27
- --ruled:#3b5fa8; --measured:#2f7d5b; --derived:#8a52a8;
49
+ --bg:#f5f5f7; --bg-elev:#ffffff; --surface:#ffffff; --surface-2:#f2f2f7;
50
+ --border:rgba(0,0,0,.08); --border-strong:rgba(0,0,0,.14);
51
+ --text:#1d1d1f; --text-muted:#6e6e73; --text-faint:#aeaeb2;
52
+ --accent:#248a3d; --accent-fill:#34c759; --accent-strong:#248a3d;
53
+ --accent-soft:rgba(52,199,89,.12); --accent-ink:#ffffff; --accent-dim:rgba(52,199,89,.45);
54
+ --blue:#007aff; --amber:#c77700; --red:#ff3b30; --violet:#af52de;
55
+ --side:#ebebf0; --sel:rgba(0,0,0,.07);
56
+ --hover:rgba(0,0,0,.04); --active:rgba(0,0,0,.07);
57
+ --shadow-sm:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.04);
58
+ --shadow:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.05);
59
+ --shadow-lg:0 4px 12px rgba(0,0,0,.06),0 20px 48px rgba(0,0,0,.12);
60
+ --scroll:rgba(0,0,0,.18); --scroll-hover:rgba(0,0,0,.3);
61
+ color-scheme:light;
28
62
  }
29
63
 
30
64
  *{box-sizing:border-box}
@@ -34,47 +68,75 @@
34
68
  just stayed on screen. Said once, here. */
35
69
  [hidden]{display:none!important}
36
70
 
71
+ html{background:var(--bg)}
37
72
  body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 var(--sans);
38
- -webkit-font-smoothing:antialiased}
73
+ font-feature-settings:"cv11","ss01","ss03";-webkit-font-smoothing:antialiased;
74
+ -moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}
39
75
  a{color:inherit}
76
+ ::selection{background:color-mix(in srgb,var(--accent) 28%,transparent)}
77
+ :focus-visible{outline:none}
78
+
79
+ /* Scrollbars that belong to the page rather than to the operating system of 2009. */
80
+ *{scrollbar-width:thin;scrollbar-color:var(--scroll) transparent}
81
+ ::-webkit-scrollbar{width:10px;height:10px}
82
+ ::-webkit-scrollbar-track{background:transparent}
83
+ ::-webkit-scrollbar-thumb{background:var(--scroll);border-radius:10px;
84
+ border:3px solid transparent;background-clip:padding-box}
85
+ ::-webkit-scrollbar-thumb:hover{background:var(--scroll-hover);background-clip:padding-box;
86
+ border:3px solid transparent}
87
+ ::-webkit-scrollbar-corner{background:transparent}
40
88
 
41
- h1{font:600 21px var(--sans);margin:0 0 4px;letter-spacing:-.015em}
42
- .sub{color:var(--ink-dim);margin:0 0 24px;font-size:13px}
89
+ h1{font:600 22px/1.25 var(--sans);margin:0 0 6px;letter-spacing:-.022em;color:var(--ink)}
90
+ .sub{color:var(--ink-dim);margin:0 0 26px;font-size:13.5px}
43
91
  .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
44
- .meta{color:var(--ink-faint);font:11.5px var(--mono)}
45
- .empty{color:var(--ink-faint);padding:26px 0;text-align:center;font-size:13px}
92
+ .meta{color:var(--ink-faint);font:11.5px var(--mono);font-variant-numeric:tabular-nums}
93
+ .empty{color:var(--ink-faint);padding:32px 0;text-align:center;font-size:13px}
46
94
  .err{color:var(--err)} .warn{color:var(--warn)} .ok{color:var(--accent)}
47
95
 
48
- .card{background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:15px 17px;margin-bottom:11px}
49
- .card h3{margin:0 0 3px;font:600 14px var(--sans)}
50
- .grid{display:grid;gap:11px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
96
+ .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);
97
+ padding:16px 18px;margin-bottom:12px;box-shadow:var(--shadow-sm);
98
+ transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
99
+ .card h3{margin:0 0 4px;font:600 14px var(--sans);letter-spacing:-.01em}
100
+ .grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
51
101
 
52
102
  /* ---------- controls ---------- */
53
- input[type=search],input[type=time],select{background:var(--panel-2);
54
- border:1px solid var(--line);color:var(--ink);
55
- border-radius:7px;padding:7px 11px;font:13px var(--sans);outline:none}
103
+ input[type=search],input[type=time],input[type=text],select{background:var(--bg-elev);
104
+ border:1px solid var(--border-strong);color:var(--ink);
105
+ border-radius:var(--r);padding:7px 11px;font:13px var(--sans);outline:none;
106
+ transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
56
107
  input[type=search]{width:280px}
57
- input[type=search]:focus,input[type=time]:focus,select:focus{border-color:var(--accent-dim)}
58
- textarea{width:100%;background:var(--panel-2);border:1px solid var(--line);color:var(--ink);
59
- border-radius:8px;padding:10px 12px;font:13px/1.55 var(--sans);outline:none;resize:vertical}
60
- textarea:focus{border-color:var(--accent-dim)}
108
+ input::placeholder,textarea::placeholder{color:var(--ink-faint)}
109
+ input[type=search]:hover,input[type=time]:hover,input[type=text]:hover,select:hover,
110
+ textarea:hover{border-color:color-mix(in srgb,var(--ink) 20%,transparent)}
111
+ input[type=search]:focus,input[type=time]:focus,input[type=text]:focus,select:focus{
112
+ border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
113
+ textarea{width:100%;background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
114
+ border-radius:var(--r);padding:10px 12px;font:13px/1.6 var(--sans);outline:none;resize:vertical;
115
+ transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
116
+ textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
61
117
 
62
118
  /* One size, whether it is a button or a link. They sit in rows together, and `a` taking its
63
119
  line-height from the font shorthand while `button` set its own made every such row a set of
64
120
  controls at two different heights. */
65
- .ghbtn{display:inline-flex;align-items:center;justify-content:center;min-height:28px;
66
- padding:5px 11px;border:1px solid var(--line);border-radius:7px;
67
- font:12px/1.4 var(--sans);color:var(--ink-dim);text-decoration:none;white-space:nowrap}
68
- .ghbtn:hover{color:var(--ink);border-color:var(--accent-dim);background:var(--panel-2)}
69
- button.ghbtn{cursor:pointer;font-family:var(--sans);background:transparent;
70
- -webkit-appearance:none;appearance:none;font-size:12px;line-height:1.4}
71
- button.ghbtn:hover{background:var(--panel-2)}
72
- .ghbtn.primary{background:var(--accent);border-color:var(--accent);color:var(--bg);font-weight:600}
73
- .ghbtn.primary:hover{filter:brightness(1.08);background:var(--accent)}
74
- .ghbtn:disabled{opacity:.5;cursor:default}
75
- .iconbtn{background:var(--panel-2);border:1px solid var(--line);color:var(--ink-dim);
76
- border-radius:7px;width:34px;height:34px;cursor:pointer;font-size:15px;line-height:1}
77
- .iconbtn:hover{color:var(--ink);border-color:var(--accent-dim)}
121
+ .ghbtn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:30px;
122
+ padding:5px 12px;border:1px solid transparent;border-radius:8px;
123
+ background:var(--surface-2);
124
+ font:500 12.5px/1.4 var(--sans);color:var(--ink);text-decoration:none;white-space:nowrap;
125
+ transition:background .15s var(--ease),color .15s var(--ease),opacity .15s var(--ease)}
126
+ .ghbtn:hover{color:var(--ink);background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
127
+ .ghbtn:active{opacity:.8}
128
+ .ghbtn:focus-visible,.btn:focus-visible,.iconbtn:focus-visible{
129
+ border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
130
+ button.ghbtn{cursor:pointer;font-family:var(--sans);
131
+ -webkit-appearance:none;appearance:none;font-size:12.5px;line-height:1.4}
132
+ button.ghbtn:hover{background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
133
+ .ghbtn.primary{background:var(--accent);border-color:transparent;color:var(--accent-ink);font-weight:600}
134
+ .ghbtn.primary:hover{background:var(--accent);color:var(--accent-ink);opacity:.88}
135
+ .ghbtn:disabled{opacity:.45;cursor:default}
136
+ .iconbtn{background:var(--surface-2);border:1px solid transparent;color:var(--ink-dim);
137
+ border-radius:8px;width:34px;height:34px;cursor:pointer;font-size:15px;line-height:1;
138
+ transition:background .15s var(--ease),color .15s var(--ease)}
139
+ .iconbtn:hover{color:var(--ink);background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
78
140
  .iconbtn{display:inline-flex;align-items:center;justify-content:center}
79
141
  .iconbtn .sync{display:inline-flex;--ic:15px}
80
142
  .iconbtn.spin .sync{animation:spin .9s linear infinite}
@@ -87,15 +149,17 @@ svg.ic{width:var(--ic,15px);height:var(--ic,15px);display:block;flex:none}
87
149
  /* A slot that holds one, centred, wherever a text glyph used to sit on a baseline. */
88
150
  .icslot{display:inline-flex;align-items:center;justify-content:center;flex:none}
89
151
 
90
- .chip{font:10px var(--sans);letter-spacing:.02em;padding:1.5px 7px;border-radius:20px;
91
- border:1px solid var(--line);color:var(--ink-faint);white-space:nowrap}
92
- .chip.hot{color:var(--err);border-color:color-mix(in srgb,var(--err) 45%,transparent)}
93
- .chip.warm{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 45%,transparent)}
94
- .chip.cool{color:var(--ruled);border-color:color-mix(in srgb,var(--ruled) 40%,transparent)}
95
- .pill{font:11px var(--mono);color:var(--ink-faint);border:1px solid var(--line);
96
- border-radius:20px;padding:2px 9px}
97
- kbd{font:11px var(--mono);border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;
98
- padding:1px 5px;color:var(--ink-dim)}
152
+ .chip{display:inline-flex;align-items:center;font:500 10.5px/1.5 var(--sans);letter-spacing:.01em;
153
+ padding:1px 8px;border-radius:999px;border:1px solid transparent;color:var(--ink-dim);
154
+ background:color-mix(in srgb,var(--ink) 7%,transparent);white-space:nowrap}
155
+ .chip.hot{color:var(--err);background:color-mix(in srgb,var(--err) 12%,transparent)}
156
+ .chip.warm{color:var(--warn);background:color-mix(in srgb,var(--warn) 13%,transparent)}
157
+ .chip.cool{color:var(--ruled);background:color-mix(in srgb,var(--ruled) 12%,transparent)}
158
+ .pill{font:11px var(--mono);color:var(--ink-dim);border:1px solid transparent;
159
+ background:color-mix(in srgb,var(--ink) 4%,transparent);
160
+ border-radius:999px;padding:2px 9px;font-variant-numeric:tabular-nums}
161
+ kbd{font:500 10.5px var(--mono);border:1px solid var(--border-strong);
162
+ border-radius:5px;padding:1px 5px;color:var(--ink-dim);background:var(--surface-2)}
99
163
 
100
164
  /* ---------- waiting ----------
101
165
  The inbox reads every repo in the org, which is seconds on a cold cache. What used to stand
@@ -103,15 +167,11 @@ kbd{font:11px var(--mono);border:1px solid var(--line);border-bottom-width:2px;b
103
167
  which reads as a page that has failed rather than one that is working. These are the shape
104
168
  of what is coming: rows in the list, a title and paragraphs in the viewer. */
105
169
  .sk{--skbg:color-mix(in srgb,var(--ink) 7%,transparent);border-radius:6px;
106
- background:linear-gradient(90deg,var(--skbg) 25%,
107
- color-mix(in srgb,var(--ink) 12%,transparent) 37%,var(--skbg) 63%);
108
- background-size:400% 100%;animation:shimmer 1.4s ease-in-out infinite}
170
+ background:var(--skbg);animation:pulse 1.4s ease-in-out infinite}
109
171
  .sk-row{background:none;animation:none;padding:11px 10px;border-top:1px solid var(--line)}
110
172
  .sk-row:first-of-type{border-top:0}
111
- .sk-row .skbar{height:11px;border-radius:6px;background:inherit;
112
- background:linear-gradient(90deg,var(--skbg) 25%,
113
- color-mix(in srgb,var(--ink) 12%,transparent) 37%,var(--skbg) 63%);
114
- background-size:400% 100%;animation:shimmer 1.4s ease-in-out infinite}
173
+ .sk-row .skbar{height:11px;border-radius:6px;
174
+ background:var(--skbg);animation:pulse 1.4s ease-in-out infinite}
115
175
  .sk-row .skbar.t{width:84%}
116
176
  .sk-row .skbar.m{width:38%;height:8px;margin-top:8px}
117
177
  /* Staggered, so the list reads as several things loading rather than one block flashing. */
@@ -125,7 +185,6 @@ kbd{font:11px var(--mono);border:1px solid var(--line);border-bottom-width:2px;b
125
185
  .sk-line:last-child{width:42%}
126
186
  /* The word beside them. It is the only part a screen reader gets, so it stays real text. */
127
187
  .skhead{color:var(--ink-faint);font-size:12.5px;padding:4px 10px 8px}
128
- @keyframes shimmer{0%{background-position:100% 0}100%{background-position:0 0}}
129
188
  /* A count that is still being fetched is not zero, and an empty badge reads as zero. */
130
189
  .nav .n.wait{display:inline-block;width:15px;height:8px;border-radius:5px;
131
190
  background:color-mix(in srgb,var(--ink) 10%,transparent);animation:pulse 1.3s ease-in-out infinite}
@@ -134,23 +193,24 @@ kbd{font:11px var(--mono);border:1px solid var(--line);border-bottom-width:2px;b
134
193
  .sk,.sk-row .skbar,.nav .n.wait{animation:none}
135
194
  }
136
195
 
137
- .notice{background:color-mix(in srgb,var(--warn) 12%,transparent);border:1px solid
138
- color-mix(in srgb,var(--warn) 40%,transparent);color:var(--ink-dim);border-radius:8px;
139
- padding:10px 13px;margin-bottom:16px;font-size:12.5px;line-height:1.6}
196
+ .notice{background:color-mix(in srgb,var(--warn) 10%,transparent);border:1px solid transparent;color:var(--ink-dim);border-radius:var(--r);
197
+ padding:11px 14px;margin-bottom:16px;font-size:12.5px;line-height:1.6}
140
198
  .notice b{color:var(--ink)}
141
199
 
142
200
  /* ---------- a dialog that asks for more than a line ----------
143
201
  Native <dialog>, so the backdrop, the focus trap and Escape are the browser's job. */
144
- dialog.ask{border:1px solid var(--line);border-radius:11px;background:var(--panel);
145
- color:var(--ink);padding:20px 22px;width:min(560px,92vw);
146
- box-shadow:0 24px 60px rgba(0,0,0,.34)}
147
- dialog.ask::backdrop{background:rgba(0,0,0,.45);backdrop-filter:blur(2px)}
148
- dialog.ask h3{margin:0;font:600 15.5px var(--sans);letter-spacing:-.012em}
149
- dialog.ask .askhint{margin:6px 0 12px;font:12.5px/1.55 var(--sans);color:var(--ink-faint)}
202
+ dialog.ask{border:1px solid var(--border-strong);border-radius:var(--r-lg);background:var(--bg-elev);
203
+ color:var(--ink);padding:22px 24px;width:min(560px,92vw);
204
+ box-shadow:var(--shadow-lg)}
205
+ dialog.ask[open]{animation:dlgin .18s var(--ease)}
206
+ @keyframes dlgin{from{opacity:0;transform:translateY(6px) scale(.985)}}
207
+ dialog.ask::backdrop{background:rgba(0,0,0,.35);backdrop-filter:blur(8px)}
208
+ dialog.ask h3{margin:0;font:600 16px var(--sans);letter-spacing:-.018em}
209
+ dialog.ask .askhint{margin:6px 0 14px;font:12.5px/1.55 var(--sans);color:var(--ink-dim)}
150
210
  dialog.ask textarea{font:13.5px/1.6 var(--sans);min-height:96px;max-height:44vh}
151
211
  /* The attach control, when the dialog is a reply rather than a question. */
152
212
  dialog.ask .attach{margin-top:9px}
153
- dialog.ask .askrow{margin-top:12px;justify-content:flex-end}
213
+ dialog.ask .askrow{margin-top:14px;justify-content:flex-end}
154
214
  dialog.ask .askrow .meta{margin-right:auto}
155
215
 
156
216
  /* The lightbox: an image, over the page, that zooms.
@@ -159,7 +219,7 @@ dialog.ask .askrow .meta{margin-right:auto}
159
219
  * from ::backdrop. 60 puts it over the `@` mention list (30) and over the dialog, which is
160
220
  * correct: a picture opened from inside a reply box should still open. */
161
221
  .lightbox{position:fixed;inset:0;z-index:60;display:flex;
162
- background:rgba(0,0,0,.82);backdrop-filter:blur(3px)}
222
+ background:rgba(0,0,0,.82)}
163
223
  /* The stage is inset rather than the whole window, and that is not decoration.
164
224
  Most images here are screenshots *of this interface*, and one fitted edge to edge reads as
165
225
  the app having navigated rather than as a picture of it. The margin of backdrop around it,
@@ -216,3 +276,16 @@ body[data-lightbox] {overflow:hidden}
216
276
 
217
277
  /* And the affordance on the document side: an image you can open should say so. */
218
278
  .md img,img.zoomable{cursor:zoom-in}
279
+
280
+ /* ---------- a dialog for a whole task (sheet() in dialog.js) ---------- */
281
+ dialog.sheet{border:1px solid var(--border-strong);border-radius:var(--r-lg);background:var(--bg);
282
+ color:var(--ink);padding:0;width:min(860px,calc(100vw - 32px));max-height:calc(100vh - 48px);
283
+ box-shadow:var(--shadow-lg)}
284
+ dialog.sheet[open]{animation:dlgin .18s var(--ease)}
285
+ dialog.sheet::backdrop{background:rgba(0,0,0,.35);backdrop-filter:blur(8px)}
286
+ .sheetbody{padding:22px 24px 26px;overflow:auto;max-height:calc(100vh - 50px)}
287
+ .sheettitle{margin:0 0 14px;font:600 18px var(--sans);letter-spacing:-.02em}
288
+ .sheetloading{display:grid;place-items:center;min-height:260px}
289
+ .spinner{width:22px;height:22px;border-radius:50%;border:2px solid var(--line);border-top-color:var(--accent);
290
+ animation:spin .7s linear infinite}
291
+ @keyframes spin{to{transform:rotate(360deg)}}
@@ -3,10 +3,11 @@
3
3
  /* ---------- navigator ----------
4
4
  Memory and Files are two kinds of thing, so they are two boxes rather than one flat list
5
5
  where a parsed section and a markdown file are told apart only by their typeface. */
6
- .navgroup{border:1px solid var(--line);border-radius:7px;margin-bottom:8px;padding:4px 3px 5px}
6
+ .navgroup{border:0;border-radius:var(--r);margin-bottom:6px;padding:2px 2px 4px}
7
+ .navgroup + .navgroup{border-top:1px solid var(--line);border-radius:0;padding-top:6px}
7
8
  .navgroup:last-child{margin-bottom:0}
8
9
  .ghead{display:flex;align-items:baseline;gap:8px;padding:6px 8px 6px;
9
- font:600 10.5px var(--sans);letter-spacing:.09em;text-transform:uppercase;color:var(--ink-faint)}
10
+ font:600 11px var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint)}
10
11
  .ghead i{font-style:normal;font:10.5px var(--mono);letter-spacing:0;text-transform:none;
11
12
  margin-left:auto;color:var(--ink-faint)}
12
13
 
@@ -20,12 +21,12 @@
20
21
  /* The section a focused fact belongs to stays lit, so opening one fact never loses your
21
22
  place in the list. */
22
23
  .tfile[data-within="true"] span.t{color:var(--ink)}
23
- .tfile[data-within="true"]{background:color-mix(in srgb,var(--panel-2) 60%,transparent)}
24
+ .tfile[data-within="true"]{background:var(--hover)}
24
25
 
25
26
  .tsurface{display:flex;align-items:center;gap:7px;width:100%;text-align:left;background:none;
26
- border:0;color:var(--ink-dim);font:11.5px var(--mono);padding:5px 8px;border-radius:5px;
27
- cursor:pointer}
28
- .tsurface:hover{background:var(--panel-2);color:var(--ink)}
27
+ border:0;color:var(--ink-dim);font:11.5px var(--mono);padding:5px 8px;border-radius:6px;
28
+ cursor:pointer;transition:background .12s var(--ease),color .12s var(--ease)}
29
+ .tsurface:hover{background:var(--hover);color:var(--ink)}
29
30
  .tsurface .caret{color:var(--ink-faint);--ic:12px;flex:none;transition:transform .12s ease}
30
31
  .tsurface[aria-expanded="false"] .caret{transform:rotate(-90deg)}
31
32
  .tsurface .t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -35,21 +36,22 @@
35
36
  .tsfiles .tfile{font-size:12px}
36
37
 
37
38
  /* ---------- memory cards ---------- */
38
- .fact{border-bottom:1px solid var(--line);padding:13px 2px;scroll-margin-top:12px}
39
+ .fact{border-bottom:1px solid var(--line);padding:15px 2px;scroll-margin-top:12px}
39
40
  .fact:last-child{border-bottom:0}
40
- .fact.lit{background:color-mix(in srgb,var(--warn) 10%,transparent);
41
- box-shadow:inset 3px 0 0 var(--warn);border-radius:0 6px 6px 0;padding-left:11px}
42
- .slug{font:12px var(--mono);color:var(--accent);cursor:pointer}
41
+ .fact.lit{background:color-mix(in srgb,var(--warn) 9%,transparent);
42
+ border-radius:8px;padding-left:12px}
43
+ .slug{font:500 12px var(--mono);color:var(--accent);cursor:pointer}
43
44
  .slug:hover{text-decoration:underline}
44
- .stmt{margin:3px 0 0}
45
+ .stmt{margin:4px 0 0;font-size:14px;color:var(--ink);letter-spacing:-.003em}
45
46
  .so{margin:4px 0 0;color:var(--ink-dim);font-size:13.2px}
46
47
  .so b{color:var(--ink);font-weight:600}
47
48
  .factsub{color:var(--ink-faint);font-size:12.5px;margin:0 0 14px}
48
- .tag{display:inline-block;font:10.5px var(--mono);padding:1px 6px;border-radius:20px;
49
- border:1px solid var(--line);color:var(--ink-faint);margin-left:6px;vertical-align:1px}
50
- .tag.ruled{color:var(--ruled);border-color:var(--ruled)}
51
- .tag.measured{color:var(--measured);border-color:var(--measured)}
52
- .tag.derived{color:var(--derived);border-color:var(--derived)}
49
+ .tag{display:inline-block;font:500 10px/1.6 var(--sans);padding:0 7px;border-radius:999px;
50
+ border:0;color:var(--ink-dim);margin-left:6px;vertical-align:1px;
51
+ background:color-mix(in srgb,var(--ink) 7%,transparent)}
52
+ .tag.ruled{color:var(--ruled);background:color-mix(in srgb,var(--ruled) 12%,transparent)}
53
+ .tag.measured{color:var(--measured);background:color-mix(in srgb,var(--accent-fill) 14%,transparent)}
54
+ .tag.derived{color:var(--derived);background:color-mix(in srgb,var(--derived) 12%,transparent)}
53
55
  .notelink{font:11.5px var(--mono);color:var(--ink-faint);cursor:pointer;margin-left:6px}
54
56
  .notelink:hover{color:var(--accent)}
55
57
 
@@ -57,8 +59,8 @@
57
59
  a way out, that read as the page breaking. */
58
60
  .focusbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 12px;
59
61
  background:color-mix(in srgb,var(--warn) 10%,transparent);
60
- border:1px solid color-mix(in srgb,var(--warn) 34%,transparent);
61
- border-radius:7px;padding:7px 8px 7px 11px;font:12px var(--sans);color:var(--ink-dim)}
62
+ border:1px solid transparent;
63
+ border-radius:var(--r);padding:7px 8px 7px 11px;font:12px var(--sans);color:var(--ink-dim)}
62
64
  .focusbar .crumb{background:none;border:0;color:var(--ink-dim);cursor:pointer;
63
65
  font:12px var(--sans);padding:0;text-align:left}
64
66
  .focusbar .crumb:hover{color:var(--ink);text-decoration:underline}
@@ -98,7 +100,8 @@
98
100
 
99
101
  /* A prompt audit finding. Not a tree row: it carries an explanation and two buttons, and
100
102
  squeezing that into the navigator's row shape is how it would get ignored. */
101
- .prob{border:1px solid var(--line);border-radius:7px;padding:9px 10px;margin:0 3px 6px}
103
+ .prob{border:1px solid var(--line);border-radius:var(--r);padding:10px 11px;margin:0 3px 6px;
104
+ background:var(--bg-elev)}
102
105
  .prob .ptitle{font:600 12.5px var(--sans);color:var(--ink)}
103
106
  .prob .pdetail{margin-top:3px;font:11.5px/1.5 var(--sans);color:var(--ink-faint)}
104
107
  .prob.error{border-color:color-mix(in srgb,var(--err) 45%,transparent)}
@@ -110,4 +113,4 @@
110
113
  /* What an edit did to the composed prompt, which is the question you were actually asking. */
111
114
  .effect{border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);
112
115
  background:color-mix(in srgb,var(--accent) 7%,transparent);
113
- border-radius:8px;padding:10px 12px;margin-bottom:14px}
116
+ border-radius:var(--r);padding:11px 13px;margin-bottom:14px}
@@ -1,24 +1,25 @@
1
1
  /* A unified diff, on the "what changed" screen. */
2
2
 
3
- .dblock{border:1px solid var(--line);border-radius:8px;overflow:hidden;margin-bottom:14px}
3
+ .dblock{border:1px solid var(--line);border-radius:var(--r);overflow:hidden;margin-bottom:14px;
4
+ box-shadow:var(--shadow-sm)}
4
5
  .dblock:last-child{margin-bottom:0}
5
- .diff{margin:0;font:12px/1.6 var(--mono);background:var(--panel-2);overflow-x:auto}
6
+ .diff{margin:0;font:12px/1.65 var(--mono);background:var(--bg);overflow-x:auto}
6
7
  .dline{display:grid;grid-template-columns:42px 42px 1fr;white-space:pre;min-width:max-content}
7
8
  .dline .ln{color:var(--ink-faint);text-align:right;padding:0 7px 0 5px;font-size:11px;
8
- user-select:none;background:color-mix(in srgb,var(--line) 34%,transparent)}
9
+ user-select:none;background:color-mix(in srgb,var(--ink) 3%,transparent)}
9
10
  .dline .tx{padding:0 14px 0 9px}
10
- .dadd{background:color-mix(in srgb,var(--accent) 15%,transparent)}
11
+ .dadd{background:color-mix(in srgb,var(--accent) 11%,transparent)}
11
12
  .dadd .tx{color:var(--ink)}
12
- .dadd .ln{background:color-mix(in srgb,var(--accent) 24%,transparent);color:var(--accent)}
13
- .ddel{background:color-mix(in srgb,var(--err) 13%,transparent)}
13
+ .dadd .ln{background:color-mix(in srgb,var(--accent) 17%,transparent);color:var(--accent)}
14
+ .ddel{background:color-mix(in srgb,var(--err) 10%,transparent)}
14
15
  .ddel .tx{color:var(--ink-dim)}
15
- .ddel .ln{background:color-mix(in srgb,var(--err) 22%,transparent);color:var(--err)}
16
- .dhunk{background:color-mix(in srgb,var(--ruled) 13%,transparent);
16
+ .ddel .ln{background:color-mix(in srgb,var(--err) 16%,transparent);color:var(--err)}
17
+ .dhunk{background:color-mix(in srgb,var(--ruled) 8%,transparent);
17
18
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
18
19
  .dhunk .tx{color:var(--ruled);font-size:11.5px}
19
20
  .dmeta .tx{color:var(--ink-faint);font-size:11.5px}
20
21
  .dfocus{box-shadow:inset 3px 0 0 var(--warn)}
21
- .dfile{font:11.5px var(--mono);color:var(--ink-dim);background:var(--panel);padding:8px 12px;
22
+ .dfile{font:11.5px var(--mono);color:var(--ink-dim);background:var(--panel);padding:9px 13px;
22
23
  border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:baseline}
23
24
  .dfile b{color:var(--ink);font-weight:600}
24
25
  .dfile .p{color:var(--accent)} .dfile .m{color:var(--err)}