@shiyuphan/follow-me 0.1.1 → 0.3.0

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 (150) hide show
  1. package/README.md +119 -69
  2. package/dist/src/cli.js +1069 -140
  3. package/dist/src/comments.js +361 -0
  4. package/dist/src/git.js +32 -3
  5. package/dist/src/host.js +106 -9
  6. package/dist/src/install.js +22 -1
  7. package/dist/src/naive.js +26 -0
  8. package/dist/src/plan.js +282 -26
  9. package/dist/src/prompt.js +711 -98
  10. package/dist/src/server.js +573 -140
  11. package/dist/src/session.js +106 -104
  12. package/dist/src/syntax.js +199 -0
  13. package/dist/src/types.js +3 -1
  14. package/dist/ui/dist/assets/abnfDiagram-WEYOIA3C-DcmzRkVZ.js +1 -0
  15. package/dist/ui/dist/assets/arc-T5QB0T1C.js +1 -0
  16. package/dist/ui/dist/assets/architecture-WOLXFQ4H-DUIkQTeE.js +1 -0
  17. package/dist/ui/dist/assets/architectureDiagram-ZHVYKT53-BUYi4-bA.js +36 -0
  18. package/dist/ui/dist/assets/array-BifhSqXX.js +1 -0
  19. package/dist/ui/dist/assets/blockDiagram-RJAE7QA3-dyJxtT9e.js +140 -0
  20. package/dist/ui/dist/assets/c4Diagram-XDPPPE54-CrAgiaZR.js +38 -0
  21. package/dist/ui/dist/assets/channel-wW7ebm0i.js +1 -0
  22. package/dist/ui/dist/assets/chunk-2E7WHVMD-C6fAf4Vj.js +88 -0
  23. package/dist/ui/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  24. package/dist/ui/dist/assets/chunk-4EMNKR2T-BLZjwZ4Q.js +1 -0
  25. package/dist/ui/dist/assets/chunk-55XCU76K-DGLK2Q-L.js +1 -0
  26. package/dist/ui/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  27. package/dist/ui/dist/assets/chunk-7S6XSTFO-BablszTX.js +2 -0
  28. package/dist/ui/dist/assets/chunk-AQDTSKZB-Cuk3B_vb.js +72 -0
  29. package/dist/ui/dist/assets/chunk-DSNLSHJZ-DiC78RqE.js +127 -0
  30. package/dist/ui/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  31. package/dist/ui/dist/assets/chunk-HAGFOTED-X_RbvixX.js +62 -0
  32. package/dist/ui/dist/assets/chunk-HNE4Q36P-C14_x5ck.js +1 -0
  33. package/dist/ui/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  34. package/dist/ui/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  35. package/dist/ui/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  36. package/dist/ui/dist/assets/chunk-NQOKRVIF-Dxd0dtYR.js +213 -0
  37. package/dist/ui/dist/assets/chunk-UHW7PFBP-BIxePNUS.js +1 -0
  38. package/dist/ui/dist/assets/chunk-VQ5W62QI-CKSKLbj4.js +1 -0
  39. package/dist/ui/dist/assets/chunk-WQQL4CDK-CWNmZGlR.js +10 -0
  40. package/dist/ui/dist/assets/chunk-XXDRQBXY-D-I5QHgG.js +1 -0
  41. package/dist/ui/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  42. package/dist/ui/dist/assets/chunk-YZO67HFG-DFP68CFd.js +1 -0
  43. package/dist/ui/dist/assets/chunk-ZAUX2LTX-DY90d4QG.js +1 -0
  44. package/dist/ui/dist/assets/chunk-ZCHMQSOR-Dlpr-5UC.js +2 -0
  45. package/dist/ui/dist/assets/classDiagram-v2-4MYWR3UA-CH9uhoLJ.js +217 -0
  46. package/dist/ui/dist/assets/cose-bilkent-JH36ORCC-DtYHrp8D.js +1 -0
  47. package/dist/ui/dist/assets/cynefin-EF2NZ3EQ-BtslbtrY.js +1 -0
  48. package/dist/ui/dist/assets/cynefinDiagram-WF24UDRP-DUU1Jcc7.js +62 -0
  49. package/dist/ui/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  50. package/dist/ui/dist/assets/dagre-NK4GIZVX-CDlP-dRW.js +4 -0
  51. package/dist/ui/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  52. package/dist/ui/dist/assets/diagram-CBK3MWTC-DiV2fn-r.js +24 -0
  53. package/dist/ui/dist/assets/diagram-I5XIIV2D-Bg5RehGi.js +24 -0
  54. package/dist/ui/dist/assets/diagram-ODIL2UCW-CFmazz3f.js +30 -0
  55. package/dist/ui/dist/assets/diagram-PFBEJUXE-uw3ZBqDp.js +3 -0
  56. package/dist/ui/dist/assets/diagram-RJA3I4VV-uEOYYRYa.js +41 -0
  57. package/dist/ui/dist/assets/diagram-TYUNFSNR-BN58LF_3.js +200 -0
  58. package/dist/ui/dist/assets/dist-tWS_lXDS.js +1 -0
  59. package/dist/ui/dist/assets/ebnfDiagram-V4P3TTPT-B68Tz-w8.js +1 -0
  60. package/dist/ui/dist/assets/elk-QONTBCTQ-DZZohNea.js +27 -0
  61. package/dist/ui/dist/assets/erDiagram-KYQIGQAA-BH_m74pv.js +99 -0
  62. package/dist/ui/dist/assets/eventmodeling-K75KTNOO-CTp5FHos.js +1 -0
  63. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-CsCvexry.js +1 -0
  64. package/dist/ui/dist/assets/ganttDiagram-R7GPNTJY-BDau7_0f.js +292 -0
  65. package/dist/ui/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  66. package/dist/ui/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  67. package/dist/ui/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  68. package/dist/ui/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  69. package/dist/ui/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  70. package/dist/ui/dist/assets/gitGraph-VSP46ZUC-Bv84IoCm.js +1 -0
  71. package/dist/ui/dist/assets/gitGraphDiagram-PDYL7XHY-DzXcMlm8.js +106 -0
  72. package/dist/ui/dist/assets/graphlib-DS17s2tU.js +1 -0
  73. package/dist/ui/dist/assets/index-BTJoqVVN.js +133 -0
  74. package/dist/ui/dist/assets/index-D60U3smc.css +1 -0
  75. package/dist/ui/dist/assets/info-OHQRW6UA-222J0O_j.js +1 -0
  76. package/dist/ui/dist/assets/infoDiagram-MVDRKAX5-DE2sa3Vz.js +2 -0
  77. package/dist/ui/dist/assets/init-C-OQMol4.js +1 -0
  78. package/dist/ui/dist/assets/ishikawaDiagram-HPEHV2GE-BJDk0Zcj.js +70 -0
  79. package/dist/ui/dist/assets/journeyDiagram-BOSHW7O7-JSUXX5Hb.js +139 -0
  80. package/dist/ui/dist/assets/kanban-definition-FNSA4HC5-Bg79b49M.js +89 -0
  81. package/dist/ui/dist/assets/katex-B_JcIwqj.js +257 -0
  82. package/dist/ui/dist/assets/line-Bk5qQSIa.js +1 -0
  83. package/dist/ui/dist/assets/linear-PFnVMru5.js +1 -0
  84. package/dist/ui/dist/assets/mermaid-parser.core-Dcxlzc-S.js +7 -0
  85. package/dist/ui/dist/assets/mermaid.core-TeOHQJ00.js +44 -0
  86. package/dist/ui/dist/assets/mindmap-definition-6OY57UG3-IaNk8NnW.js +98 -0
  87. package/dist/ui/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  88. package/dist/ui/dist/assets/packet-JDAUHWVQ-DMff5cVK.js +1 -0
  89. package/dist/ui/dist/assets/path-fybaL0A-.js +1 -0
  90. package/dist/ui/dist/assets/pegDiagram-ECIUJNBX-66Xu4YRc.js +1 -0
  91. package/dist/ui/dist/assets/pie-XZMESJXO-GEtzLCIM.js +1 -0
  92. package/dist/ui/dist/assets/pieDiagram-GNMPY74T-7jMBi-kU.js +39 -0
  93. package/dist/ui/dist/assets/prism-bash-D6zCJ74D.js +1 -0
  94. package/dist/ui/dist/assets/prism-c-04YixN25.js +1 -0
  95. package/dist/ui/dist/assets/prism-clike-DapgWyxx.js +1 -0
  96. package/dist/ui/dist/assets/prism-core-FDHlWQ43.js +1 -0
  97. package/dist/ui/dist/assets/prism-cpp-C-lJbC-6.js +1 -0
  98. package/dist/ui/dist/assets/prism-css-CPgOzxXp.js +1 -0
  99. package/dist/ui/dist/assets/prism-go-C_8qAH5n.js +1 -0
  100. package/dist/ui/dist/assets/prism-java-6wtusvIl.js +1 -0
  101. package/dist/ui/dist/assets/prism-javascript-BnO-swvr.js +1 -0
  102. package/dist/ui/dist/assets/prism-json-Dp_-W-Hv.js +1 -0
  103. package/dist/ui/dist/assets/prism-jsx-D-D0NWtC.js +1 -0
  104. package/dist/ui/dist/assets/prism-kotlin-BUlJgC-o.js +1 -0
  105. package/dist/ui/dist/assets/prism-markdown-Bc9-36OC.js +1 -0
  106. package/dist/ui/dist/assets/prism-markup-Cp04rIa1.js +1 -0
  107. package/dist/ui/dist/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
  108. package/dist/ui/dist/assets/prism-php-Co4fM40K.js +1 -0
  109. package/dist/ui/dist/assets/prism-python-CfiSVtM6.js +1 -0
  110. package/dist/ui/dist/assets/prism-ruby-DFCASkuD.js +1 -0
  111. package/dist/ui/dist/assets/prism-rust-Bu9N8X3N.js +1 -0
  112. package/dist/ui/dist/assets/prism-sql-CYhVDMEE.js +1 -0
  113. package/dist/ui/dist/assets/prism-swift-B0kWc-ne.js +1 -0
  114. package/dist/ui/dist/assets/prism-toml-DKb54GAT.js +1 -0
  115. package/dist/ui/dist/assets/prism-tsx-p6J2Kc_4.js +1 -0
  116. package/dist/ui/dist/assets/prism-typescript-BMqONzxf.js +1 -0
  117. package/dist/ui/dist/assets/prism-yaml-C0JHu8gr.js +1 -0
  118. package/dist/ui/dist/assets/quadrantDiagram-D57K4MC3-8hIWayC2.js +7 -0
  119. package/dist/ui/dist/assets/radar-ABXABTNO-BTdwUOOF.js +1 -0
  120. package/dist/ui/dist/assets/railroad-I3PHUGI6-DcMbDqBL.js +1 -0
  121. package/dist/ui/dist/assets/railroad-abnf-LNEFI6M7-gTzUTWeI.js +1 -0
  122. package/dist/ui/dist/assets/railroad-ebnf-DWYD2UWJ-CNtdUPtk.js +1 -0
  123. package/dist/ui/dist/assets/railroad-peg-JR7BVNR7-DOiExAtE.js +1 -0
  124. package/dist/ui/dist/assets/railroadDiagram-Y72NRBL7-2kE_ygvM.js +1 -0
  125. package/dist/ui/dist/assets/requirementDiagram-EQYSW5RA-CoIAHRq2.js +84 -0
  126. package/dist/ui/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  127. package/dist/ui/dist/assets/sankeyDiagram-SY24JHO5-7p8D3wim.js +40 -0
  128. package/dist/ui/dist/assets/sequenceDiagram-BLEHNHQU-C6QFnMvn.js +169 -0
  129. package/dist/ui/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  130. package/dist/ui/dist/assets/src-FgijoBVs.js +1 -0
  131. package/dist/ui/dist/assets/stateDiagram-v2-A6IESKWZ-n9eAb_bT.js +291 -0
  132. package/dist/ui/dist/assets/swimlanes-B3ENRKFQ-U18mtUzX.js +1 -0
  133. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DX07QVy9.js +8 -0
  134. package/dist/ui/dist/assets/timeline-definition-JXYKGLGB-CD2Z8QGP.js +120 -0
  135. package/dist/ui/dist/assets/treeView-D4JQ5SDB-sqUjH5gB.js +1 -0
  136. package/dist/ui/dist/assets/treemap-SAJKECNS-DvZ38FP7.js +1 -0
  137. package/dist/ui/dist/assets/usecaseDiagram-WPOLBNNX-CqOGidaZ.js +328 -0
  138. package/dist/ui/dist/assets/vennDiagram-Q3Y3IDCW-D1SXSBQ5.js +34 -0
  139. package/dist/ui/dist/assets/wardley-7MLQ67FV-Bpwio-Dx.js +1 -0
  140. package/dist/ui/dist/assets/wardleyDiagram-IVAIDKWA-JSUXHMnk.js +78 -0
  141. package/dist/ui/dist/assets/xychartDiagram-4URNHDU4-B4IFqQTN.js +7 -0
  142. package/dist/ui/dist/index.html +15 -0
  143. package/dist/ui/dist/style.css +121 -0
  144. package/package.json +31 -8
  145. package/skills/follow-me/SKILL.md +15 -61
  146. package/dist/src/anthropic.js +0 -101
  147. package/dist/ui/app.js +0 -797
  148. package/dist/ui/index.html +0 -40
  149. package/dist/ui/playback.js +0 -175
  150. package/dist/ui/style.css +0 -466
@@ -1,40 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="color-scheme" content="light dark" />
7
- <title>follow-me review</title>
8
- <link rel="stylesheet" href="/style.css" />
9
- </head>
10
- <body>
11
- <header class="topbar">
12
- <strong>follow-me</strong>
13
- <span id="summary" class="muted"></span>
14
- <nav id="sessions" class="sessions" aria-label="Sessions" hidden></nav>
15
- <span id="status" class="status" role="status"></span>
16
- <button id="view-toggle" type="button" title="Play the branch commit by commit (space)">
17
- Play
18
- </button>
19
- <button id="end" type="button" class="primary">End review</button>
20
- </header>
21
- <section id="player" class="player" aria-label="Playback" hidden>
22
- <button id="play" type="button" class="primary" title="Play or pause (space)">Play</button>
23
- <button id="prev" type="button" title="Previous commit (p)">Previous</button>
24
- <button id="next" type="button" title="Next commit (n)">Next</button>
25
- <label>Speed <select id="speed"></select></label>
26
- <label>Voice <select id="voice"></select></label>
27
- <button id="mute" type="button" aria-pressed="false">Mute</button>
28
- <p id="caption" class="caption" aria-live="polite"></p>
29
- </section>
30
- <div id="app" class="layout">
31
- <nav class="sidebar" aria-label="Commits">
32
- <ol id="commits" class="commit-list"></ol>
33
- <section id="plan-note" class="plan-note"></section>
34
- </nav>
35
- <main id="main" class="main"></main>
36
- </div>
37
- <div id="done" class="done" hidden></div>
38
- <script src="/app.js" type="module"></script>
39
- </body>
40
- </html>
@@ -1,175 +0,0 @@
1
- // @ts-check
2
- // Pure playback logic. The browser imports it from /playback.js and the tests import it
3
- // directly, so nothing here may touch the DOM or speech APIs.
4
-
5
- /**
6
- * @typedef {{ op: " ", text: string } | { op: "+" | "-", text: string, change: number }} HunkLine
7
- * @typedef {{ kind: "hunk", id: string, path: string, body: HunkLine[] }
8
- * | { kind: "file" | "mode", id: string, path: string }} Unit
9
- * @typedef {{ kind: "rest" } | { kind: "lines", changes: number[] }} Selection
10
- * @typedef {{ unit: string, select: Selection, narration?: string }} Ref
11
- * @typedef {{ index: number, message: string, narration?: string, refs: Ref[] }} Commit
12
- * @typedef {{ kind: "commit", commit: number, say: string }
13
- * | { kind: "unit", commit: number, unit: string, say: string | null }
14
- * | { kind: "line", commit: number, unit: string, line: number, change: number, op: "+" | "-" }} Step
15
- * @typedef {"hidden" | "shown" | "gone"} LineState
16
- * @typedef {{ name: string, lang: string, default: boolean }} VoiceInfo
17
- */
18
-
19
- export const SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
20
-
21
- const LINES_PER_SECOND = 4;
22
- const COMMIT_BEAT_MS = 700;
23
- const UNIT_BEAT_MS = 350;
24
- const WORDS_PER_SECOND = 2.5;
25
-
26
- /**
27
- * The key a diff line carries in the DOM, unit id plus its index in the hunk body.
28
- * @param {string} unit
29
- * @param {number} line
30
- */
31
- export function lineKey(unit, line) {
32
- return `${unit}:${line}`;
33
- }
34
-
35
- /**
36
- * What is spoken when a commit starts: its narration, else its title.
37
- * @param {Commit} commit
38
- */
39
- export function commitNarration(commit) {
40
- const narration = commit.narration?.trim();
41
- if (narration) return narration;
42
- return commit.message.split("\n")[0] ?? "";
43
- }
44
-
45
- /**
46
- * Every step of the playback in order: a commit begins, each of its units begins, and each of
47
- * the commit's own change lines appears (+) or fades out (-) in hunk order.
48
- * @param {readonly Commit[]} commits
49
- * @param {readonly Unit[]} units
50
- * @returns {Step[]}
51
- */
52
- export function buildScript(commits, units) {
53
- const byId = new Map(units.map((u) => [u.id, u]));
54
- /** @type {Step[]} */
55
- const steps = [];
56
- for (const commit of commits) {
57
- steps.push({ kind: "commit", commit: commit.index, say: commitNarration(commit) });
58
- for (const ref of commit.refs) {
59
- const unit = byId.get(ref.unit);
60
- if (unit === undefined) continue;
61
- const say = ref.narration?.trim() || null;
62
- steps.push({ kind: "unit", commit: commit.index, unit: unit.id, say });
63
- if (unit.kind !== "hunk" || ref.select.kind !== "lines") continue;
64
- const mine = new Set(ref.select.changes);
65
- unit.body.forEach((line, i) => {
66
- if (line.op === " " || !mine.has(line.change)) return;
67
- steps.push({
68
- kind: "line",
69
- commit: commit.index,
70
- unit: unit.id,
71
- line: i,
72
- change: line.change,
73
- op: line.op,
74
- });
75
- });
76
- }
77
- }
78
- return steps;
79
- }
80
-
81
- /**
82
- * How long to rest after a step at the given speed. Lines arrive a few per second; removals
83
- * take half as long because nothing new needs reading.
84
- * @param {Step} step
85
- * @param {number} speed
86
- */
87
- export function stepDelay(step, speed) {
88
- if (step.kind === "commit") return COMMIT_BEAT_MS / speed;
89
- if (step.kind === "unit") return UNIT_BEAT_MS / speed;
90
- const line = 1000 / (LINES_PER_SECOND * speed);
91
- return step.op === "+" ? line : line / 2;
92
- }
93
-
94
- /**
95
- * A generous upper bound on how long speaking the text takes, for when the browser never
96
- * reports the end of an utterance.
97
- * @param {string} text
98
- * @param {number} speed
99
- */
100
- export function speechTimeout(text, speed) {
101
- const words = text.split(/\s+/).filter(Boolean).length;
102
- return (2 * 1000 * words) / (WORDS_PER_SECOND * speed) + 2000;
103
- }
104
-
105
- /**
106
- * The first step of a commit, clamped to the script. Seeking past the end lands on the end.
107
- * @param {readonly Step[]} script
108
- * @param {number} commit
109
- */
110
- export function commitStart(script, commit) {
111
- const at = script.findIndex((s) => s.kind === "commit" && s.commit === commit);
112
- if (at !== -1) return at;
113
- return commit < 0 ? 0 : script.length;
114
- }
115
-
116
- /**
117
- * The commit on screen at a position: the one whose step was performed last.
118
- * @param {readonly Step[]} script
119
- * @param {number} position
120
- */
121
- export function commitAt(script, position) {
122
- return script[Math.max(0, Math.min(position, script.length) - 1)]?.commit ?? 0;
123
- }
124
-
125
- /**
126
- * How each change line of a commit looks after `position` steps, and which line was written
127
- * last. Added lines are hidden until typed; removed lines stay shown until their step.
128
- * @param {readonly Step[]} script
129
- * @param {number} position
130
- * @param {number} commit
131
- * @returns {{ states: Map<string, LineState>, current: string | null }}
132
- */
133
- export function lineStates(script, position, commit) {
134
- /** @type {Map<string, LineState>} */
135
- const states = new Map();
136
- /** @type {string | null} */
137
- let current = null;
138
- script.forEach((step, i) => {
139
- if (step.kind !== "line" || step.commit !== commit) return;
140
- const key = lineKey(step.unit, step.line);
141
- const done = i < position;
142
- if (step.op === "+") states.set(key, done ? "shown" : "hidden");
143
- else states.set(key, done ? "gone" : "shown");
144
- if (done) current = key;
145
- });
146
- if (commitAt(script, position) !== commit) current = null;
147
- return { states, current };
148
- }
149
-
150
- /**
151
- * A speed from storage or a select, snapped to the offered steps.
152
- * @param {unknown} value
153
- */
154
- export function parseSpeed(value) {
155
- const n = Number(value);
156
- return SPEEDS.includes(n) ? n : 1;
157
- }
158
-
159
- /**
160
- * The voice to preselect: the saved one if the browser still has it, else the browser's
161
- * default, else the first voice in the reader's language, else none.
162
- * @param {readonly VoiceInfo[]} voices
163
- * @param {string | null} saved
164
- * @param {string} language
165
- * @returns {string | null}
166
- */
167
- export function pickVoice(voices, saved, language) {
168
- if (saved !== null && voices.some((v) => v.name === saved)) return saved;
169
- const prefix = language.split("-")[0] ?? "";
170
- const match =
171
- voices.find((v) => v.default && v.lang.startsWith(prefix)) ??
172
- voices.find((v) => v.lang === language) ??
173
- voices.find((v) => v.lang.startsWith(prefix));
174
- return match?.name ?? null;
175
- }
package/dist/ui/style.css DELETED
@@ -1,466 +0,0 @@
1
- :root {
2
- color-scheme: light dark;
3
- --bg: #ffffff;
4
- --panel: #f6f7f9;
5
- --border: #d8dce2;
6
- --text: #1c2026;
7
- --muted: #6a7280;
8
- --accent: #2459d6;
9
- --accent-text: #ffffff;
10
- --add-bg: #e3f6e8;
11
- --add-text: #0f5a26;
12
- --del-bg: #fde7e7;
13
- --del-text: #8a1c1c;
14
- --select-bg: #fff3c4;
15
- --comment-bg: #eef3ff;
16
- --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
17
- }
18
-
19
- @media (prefers-color-scheme: dark) {
20
- :root {
21
- --bg: #14171c;
22
- --panel: #1c2027;
23
- --border: #2f353f;
24
- --text: #e4e7ec;
25
- --muted: #8d96a5;
26
- --accent: #6c9bff;
27
- --accent-text: #0b1220;
28
- --add-bg: #12301c;
29
- --add-text: #7fdc9a;
30
- --del-bg: #3a1a1c;
31
- --del-text: #ff9b9b;
32
- --select-bg: #4a4012;
33
- --comment-bg: #1b2538;
34
- }
35
- }
36
-
37
- * {
38
- box-sizing: border-box;
39
- }
40
-
41
- body {
42
- margin: 0;
43
- background: var(--bg);
44
- color: var(--text);
45
- font:
46
- 14px / 1.5 system-ui,
47
- sans-serif;
48
- height: 100vh;
49
- display: flex;
50
- flex-direction: column;
51
- }
52
-
53
- .muted {
54
- color: var(--muted);
55
- }
56
-
57
- .topbar {
58
- display: flex;
59
- align-items: center;
60
- gap: 12px;
61
- padding: 8px 16px;
62
- border-bottom: 1px solid var(--border);
63
- background: var(--panel);
64
- }
65
-
66
- .sessions {
67
- display: flex;
68
- gap: 8px;
69
- font-size: 13px;
70
- }
71
-
72
- .sessions[hidden] {
73
- display: none;
74
- }
75
-
76
- .sessions a {
77
- color: var(--accent);
78
- }
79
-
80
- .sessions a[aria-current="page"] {
81
- color: var(--text);
82
- font-weight: 600;
83
- text-decoration: none;
84
- }
85
-
86
- body.index {
87
- display: block;
88
- height: auto;
89
- padding: 16px 24px;
90
- }
91
-
92
- .session-index {
93
- padding-left: 18px;
94
- }
95
-
96
- .session-index a {
97
- color: var(--accent);
98
- font-weight: 600;
99
- }
100
-
101
- .status {
102
- margin-left: auto;
103
- color: var(--del-text);
104
- }
105
-
106
- button {
107
- font: inherit;
108
- padding: 3px 10px;
109
- border: 1px solid var(--border);
110
- border-radius: 6px;
111
- background: var(--bg);
112
- color: var(--text);
113
- cursor: pointer;
114
- }
115
-
116
- button:hover {
117
- border-color: var(--accent);
118
- }
119
-
120
- button.primary {
121
- background: var(--accent);
122
- color: var(--accent-text);
123
- border-color: var(--accent);
124
- }
125
-
126
- button.link {
127
- border: none;
128
- background: none;
129
- color: var(--accent);
130
- padding: 0 4px;
131
- }
132
-
133
- .layout {
134
- flex: 1;
135
- display: grid;
136
- grid-template-columns: 300px 1fr;
137
- min-height: 0;
138
- }
139
-
140
- .sidebar {
141
- display: flex;
142
- flex-direction: column;
143
- border-right: 1px solid var(--border);
144
- background: var(--panel);
145
- min-height: 0;
146
- }
147
-
148
- .commit-list {
149
- list-style: none;
150
- margin: 0;
151
- padding: 0;
152
- overflow-y: auto;
153
- flex: 1;
154
- }
155
-
156
- .commit-list li {
157
- display: flex;
158
- gap: 8px;
159
- padding: 8px 12px;
160
- cursor: pointer;
161
- border-left: 3px solid transparent;
162
- }
163
-
164
- .commit-list li:hover {
165
- background: var(--bg);
166
- }
167
-
168
- .commit-list li.selected {
169
- background: var(--bg);
170
- border-left-color: var(--accent);
171
- font-weight: 600;
172
- }
173
-
174
- .commit-list .index {
175
- color: var(--muted);
176
- min-width: 2ch;
177
- text-align: right;
178
- }
179
-
180
- .commit-list .title {
181
- flex: 1;
182
- overflow: hidden;
183
- text-overflow: ellipsis;
184
- white-space: nowrap;
185
- }
186
-
187
- .badge {
188
- background: var(--accent);
189
- color: var(--accent-text);
190
- border-radius: 10px;
191
- padding: 0 7px;
192
- font-size: 12px;
193
- }
194
-
195
- .plan-note {
196
- border-top: 1px solid var(--border);
197
- padding: 10px 12px;
198
- }
199
-
200
- .main {
201
- overflow-y: auto;
202
- padding: 16px 24px 64px;
203
- }
204
-
205
- .commit-head h2 {
206
- margin: 0 0 4px;
207
- font-size: 16px;
208
- }
209
-
210
- .message {
211
- font-family: var(--font-mono);
212
- white-space: pre-wrap;
213
- margin: 4px 0 8px;
214
- padding: 8px 12px;
215
- background: var(--panel);
216
- border-radius: 6px;
217
- }
218
-
219
- .controls {
220
- display: flex;
221
- gap: 8px;
222
- margin: 4px 0;
223
- }
224
-
225
- .file-group {
226
- margin-top: 20px;
227
- }
228
-
229
- .file-group > h3 {
230
- margin: 0 0 6px;
231
- font: 600 13px var(--font-mono);
232
- color: var(--muted);
233
- }
234
-
235
- .unit {
236
- border: 1px solid var(--border);
237
- border-radius: 6px;
238
- margin-bottom: 12px;
239
- overflow: hidden;
240
- }
241
-
242
- .unit-head {
243
- display: flex;
244
- gap: 8px;
245
- align-items: center;
246
- padding: 3px 10px;
247
- background: var(--panel);
248
- color: var(--muted);
249
- font: 12px var(--font-mono);
250
- }
251
-
252
- .unit-head .spacer {
253
- flex: 1;
254
- }
255
-
256
- .card {
257
- padding: 6px 10px;
258
- font-family: var(--font-mono);
259
- }
260
-
261
- .diff {
262
- font: 12.5px / 1.5 var(--font-mono);
263
- overflow-x: auto;
264
- }
265
-
266
- .dl {
267
- display: flex;
268
- white-space: pre;
269
- min-width: max-content;
270
- max-height: 2em;
271
- }
272
-
273
- .dl .gutter {
274
- width: 4ch;
275
- text-align: right;
276
- padding-right: 6px;
277
- color: var(--muted);
278
- user-select: none;
279
- }
280
-
281
- .dl .op {
282
- width: 2ch;
283
- user-select: none;
284
- }
285
-
286
- .dl .label {
287
- margin-left: 12px;
288
- font-size: 11px;
289
- color: var(--muted);
290
- font-style: italic;
291
- }
292
-
293
- .dl.add.mine {
294
- background: var(--add-bg);
295
- color: var(--add-text);
296
- }
297
-
298
- .dl.del.mine {
299
- background: var(--del-bg);
300
- color: var(--del-text);
301
- }
302
-
303
- .dl.other {
304
- opacity: 0.4;
305
- }
306
-
307
- .dl.mine.pickable {
308
- cursor: pointer;
309
- }
310
-
311
- .dl.picked {
312
- outline: 2px solid var(--accent);
313
- outline-offset: -2px;
314
- background: var(--select-bg);
315
- color: var(--text);
316
- }
317
-
318
- .comments {
319
- padding: 0 10px;
320
- }
321
-
322
- .comment {
323
- background: var(--comment-bg);
324
- border-radius: 6px;
325
- margin: 8px 0;
326
- padding: 6px 10px;
327
- }
328
-
329
- .comment .meta {
330
- color: var(--muted);
331
- font-size: 12px;
332
- }
333
-
334
- .comment .text {
335
- white-space: pre-wrap;
336
- }
337
-
338
- textarea {
339
- width: 100%;
340
- min-height: 64px;
341
- font: inherit;
342
- padding: 6px 8px;
343
- color: var(--text);
344
- background: var(--bg);
345
- border: 1px solid var(--border);
346
- border-radius: 6px;
347
- resize: vertical;
348
- }
349
-
350
- .box {
351
- margin: 8px 0;
352
- }
353
-
354
- .box .row {
355
- display: flex;
356
- gap: 8px;
357
- margin-top: 4px;
358
- }
359
-
360
- .done {
361
- position: fixed;
362
- inset: 0;
363
- display: grid;
364
- place-items: center;
365
- background: var(--bg);
366
- font-size: 16px;
367
- padding: 24px;
368
- text-align: center;
369
- }
370
-
371
- .done[hidden] {
372
- display: none;
373
- }
374
-
375
- .done code {
376
- font-family: var(--font-mono);
377
- background: var(--panel);
378
- padding: 1px 6px;
379
- border-radius: 4px;
380
- }
381
-
382
- .player {
383
- display: flex;
384
- flex-wrap: wrap;
385
- align-items: center;
386
- gap: 8px 12px;
387
- padding: 6px 16px;
388
- border-bottom: 1px solid var(--border);
389
- background: var(--panel);
390
- }
391
-
392
- .player[hidden] {
393
- display: none;
394
- }
395
-
396
- .player label {
397
- display: flex;
398
- align-items: center;
399
- gap: 6px;
400
- color: var(--muted);
401
- }
402
-
403
- select {
404
- font: inherit;
405
- padding: 2px 6px;
406
- border: 1px solid var(--border);
407
- border-radius: 6px;
408
- background: var(--bg);
409
- color: var(--text);
410
- max-width: 220px;
411
- }
412
-
413
- button:disabled,
414
- select:disabled {
415
- opacity: 0.5;
416
- cursor: default;
417
- }
418
-
419
- .caption {
420
- flex: 1 1 320px;
421
- margin: 0;
422
- font-style: italic;
423
- color: var(--text);
424
- }
425
-
426
- .dl.pending {
427
- display: none;
428
- }
429
-
430
- .dl.gone {
431
- opacity: 0;
432
- max-height: 0;
433
- overflow: hidden;
434
- transition:
435
- opacity 0.5s ease,
436
- max-height 0.3s ease 0.5s;
437
- }
438
-
439
- .dl.current {
440
- outline: 2px solid var(--accent);
441
- outline-offset: -2px;
442
- animation: type-in 0.25s steps(16, end);
443
- }
444
-
445
- .dl.gone.current {
446
- opacity: 0.45;
447
- max-height: 2em;
448
- text-decoration: line-through;
449
- }
450
-
451
- @keyframes type-in {
452
- from {
453
- clip-path: inset(0 100% 0 0);
454
- }
455
- to {
456
- clip-path: inset(0 0 0 0);
457
- }
458
- }
459
-
460
- @media (prefers-reduced-motion: reduce) {
461
- .dl.current,
462
- .dl.gone {
463
- animation: none;
464
- transition: none;
465
- }
466
- }