@litfamily/litopencode 1.0.31 → 1.0.33

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 (282) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README-Ko-KR.md +6 -6
  3. package/README.md +6 -6
  4. package/TRADEMARK.md +12 -0
  5. package/dist/activation-primary-prompts.js +5 -1
  6. package/dist/activation-probe.js +1 -0
  7. package/dist/activation-prompt-utils.d.ts +1 -1
  8. package/dist/activation-reentry.d.ts +20 -0
  9. package/dist/activation-reentry.js +110 -1
  10. package/dist/activation-routing.d.ts +2 -1
  11. package/dist/activation-routing.js +100 -11
  12. package/dist/activation-workflow-prompts.d.ts +4 -0
  13. package/dist/activation-workflow-prompts.js +30 -0
  14. package/dist/activation.d.ts +1 -1
  15. package/dist/activation.js +44 -5
  16. package/dist/adhd-next.d.ts +51 -0
  17. package/dist/adhd-next.js +150 -0
  18. package/dist/agents/defaults.js +1 -1
  19. package/dist/cli/managed-skill-assets.d.ts +1 -1
  20. package/dist/cli/managed-skill-assets.js +7 -1
  21. package/dist/cli/native-skill-integrity.js +1 -1
  22. package/dist/cli.js +7 -1
  23. package/dist/commands.d.ts +28 -1
  24. package/dist/commands.js +58 -5
  25. package/dist/durable-plan.d.ts +5 -1
  26. package/dist/durable-plan.js +14 -2
  27. package/dist/features.d.ts +80 -1
  28. package/dist/features.js +105 -1
  29. package/dist/hooks.js +3 -0
  30. package/dist/ledger.js +12 -1
  31. package/dist/plan-scope-roots.d.ts +23 -0
  32. package/dist/plan-scope-roots.js +116 -0
  33. package/dist/restart-notice.d.ts +7 -0
  34. package/dist/restart-notice.js +40 -0
  35. package/dist/secret-shapes.js +5 -5
  36. package/dist/server.js +4 -0
  37. package/dist/session-liveness.d.ts +20 -0
  38. package/dist/session-liveness.js +99 -0
  39. package/dist/skills.d.ts +22 -1
  40. package/dist/skills.js +36 -0
  41. package/dist/start-work-typed.d.ts +34 -0
  42. package/dist/start-work-typed.js +167 -0
  43. package/dist/state.d.ts +3 -0
  44. package/dist/state.js +3 -0
  45. package/dist/tools.js +22 -2
  46. package/dist/tui/mission-band.d.ts +22 -1
  47. package/dist/tui/mission-band.js +75 -16
  48. package/dist/tui/mission-looks.d.ts +38 -0
  49. package/dist/tui/mission-looks.js +73 -0
  50. package/dist/tui/mission-model.d.ts +2 -0
  51. package/dist/tui/mission-model.js +3 -1
  52. package/dist/tui/mission-rainbow.d.ts +33 -0
  53. package/dist/tui/mission-rainbow.js +90 -0
  54. package/dist/tui/mission-source.d.ts +49 -0
  55. package/dist/tui/mission-source.js +217 -2
  56. package/dist/tui/mission-text.d.ts +2 -0
  57. package/dist/tui/mission-text.js +9 -1
  58. package/dist/tui/mission-threads.d.ts +58 -0
  59. package/dist/tui/mission-threads.js +128 -0
  60. package/dist/tui.js +112 -7
  61. package/dist/uiux-visual-catalog.js +8 -1
  62. package/dist/v2/events.js +3 -0
  63. package/dist/v2/session-hooks.js +38 -5
  64. package/dist/workflow-stack.d.ts +53 -0
  65. package/dist/workflow-stack.js +196 -0
  66. package/docs/assets/readme/badge-version.svg +1 -1
  67. package/docs/assets/skills/lit-schematic.webp +0 -0
  68. package/docs/reference-Ko-KR.md +48 -3
  69. package/docs/reference.md +54 -3
  70. package/package.json +1 -1
  71. package/skills/frontend-ui-ux/SKILL.md +3 -2
  72. package/skills/frontend-ui-ux/kit/components.css +414 -0
  73. package/skills/frontend-ui-ux/kit/fonts.json +22 -0
  74. package/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  75. package/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  76. package/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  77. package/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  78. package/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  79. package/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  80. package/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  81. package/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  82. package/skills/frontend-ui-ux/kit/ui.js +226 -0
  83. package/skills/frontend-ui-ux/references/brand-and-imagery.md +2 -2
  84. package/skills/frontend-ui-ux/references/complete-contract.md +2 -1
  85. package/skills/frontend-ui-ux/references/craft-floor.md +6 -2
  86. package/skills/frontend-ui-ux/references/default-editorial-pixel.json +3 -0
  87. package/skills/frontend-ui-ux/references/house-styles.md +236 -0
  88. package/skills/frontend-ui-ux/references/motion-guide.md +2 -2
  89. package/skills/frontend-ui-ux/references/production.md +3 -1
  90. package/skills/frontend-ui-ux/references/slop-register.md +4 -4
  91. package/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  92. package/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
  93. package/skills/frontend-ui-ux/scripts/probe-lock.mjs +235 -0
  94. package/skills/frontend-ui-ux/scripts/probe-page.mjs +34 -12
  95. package/skills/frontend-ui-ux/scripts/probe-static.mjs +16 -3
  96. package/skills/frontend-ui-ux/scripts/probe-thresholds.json +8 -2
  97. package/skills/frontend-ui-ux/scripts/probe.mjs +35 -7
  98. package/skills/lit-adhd/SKILL.md +209 -0
  99. package/skills/lit-adhd/references/brief-format.md +46 -0
  100. package/skills/lit-adhd/references/card-examples.md +103 -0
  101. package/skills/lit-adhd/references/status-taxonomy.md +34 -0
  102. package/skills/lit-adhd/scripts/rewind.mjs +933 -0
  103. package/skills/lit-diagram-drawer/SKILL.md +4 -3
  104. package/skills/lit-diagram-drawer/references/style-guide.md +20 -11
  105. package/skills/lit-diagram-drawer/references/type-architecture.md +3 -3
  106. package/skills/lit-diagram-drawer/references/type-dependency.md +1 -1
  107. package/skills/lit-diagram-drawer/references/type-tree.md +3 -3
  108. package/skills/lit-diagram-drawer/references/type-wardley.md +1 -1
  109. package/skills/lit-diagram-drawer/references/verifier-guide.md +1 -0
  110. package/skills/lit-diagram-drawer/scripts/craft-thresholds.json +1 -2
  111. package/skills/lit-diagram-drawer/scripts/visual-quality.mjs +1 -2
  112. package/skills/lit-docx/SKILL.md +2 -1
  113. package/skills/lit-handoff/SKILL.md +2 -0
  114. package/skills/lit-hijack/SKILL.md +422 -0
  115. package/skills/lit-hijack/references/capture-and-provenance.md +87 -0
  116. package/skills/lit-hijack/references/clean-room-build.md +111 -0
  117. package/skills/lit-hijack/references/distil-and-licence.md +100 -0
  118. package/skills/lit-hijack/references/gap-and-port-design.md +126 -0
  119. package/skills/lit-hijack/references/parity-and-report.md +110 -0
  120. package/skills/lit-hijack/scripts/hijack.mjs +993 -0
  121. package/skills/lit-plan/SKILL.md +8 -0
  122. package/skills/lit-pptx/SKILL.md +27 -11
  123. package/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  124. package/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  125. package/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  126. package/skills/lit-pptx/fonts/jetbrains-mono/SOURCE.md +17 -0
  127. package/skills/lit-pptx/references/density-and-fill.md +9 -3
  128. package/skills/lit-pptx/references/direction-step.md +21 -6
  129. package/skills/lit-pptx/references/examples/04-lecture-chalk-ko.md +3 -6
  130. package/skills/lit-pptx/references/examples/05-research-talk-paper-en.md +1 -5
  131. package/skills/lit-pptx/references/examples/08-reliability-keynote-night-en.md +0 -4
  132. package/skills/lit-pptx/references/examples/09-status-update-ledger-en.md +2 -2
  133. package/skills/lit-pptx/references/examples/11-forecast-review-night-ko.md +4 -7
  134. package/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
  135. package/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
  136. package/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
  137. package/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
  138. package/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
  139. package/skills/lit-pptx/references/layout-families.md +65 -47
  140. package/skills/lit-pptx/references/title-treatments.md +83 -134
  141. package/skills/lit-pptx/references/tonalities/atlas.md +3 -3
  142. package/skills/lit-pptx/references/tonalities/bauhaus.md +140 -0
  143. package/skills/lit-pptx/references/tonalities/chalk.md +7 -6
  144. package/skills/lit-pptx/references/tonalities/computational-brutalism.md +150 -0
  145. package/skills/lit-pptx/references/tonalities/gazette.md +1 -1
  146. package/skills/lit-pptx/references/tonalities/ledger.md +1 -1
  147. package/skills/lit-pptx/references/tonalities/neo-brutalism.md +161 -0
  148. package/skills/lit-pptx/references/tonalities/newsroom.md +144 -0
  149. package/skills/lit-pptx/references/tonalities/night.md +1 -1
  150. package/skills/lit-pptx/references/tonalities/paper.md +2 -2
  151. package/skills/lit-pptx/references/tonalities/riso.md +137 -0
  152. package/skills/lit-pptx/references/tonalities/signal.md +3 -3
  153. package/skills/lit-pptx/references/tonalities/studio.md +7 -6
  154. package/skills/lit-pptx/run.mjs +2 -1
  155. package/skills/lit-pptx/scripts/compile-deck.js +1 -1
  156. package/skills/lit-pptx/scripts/craft-thresholds.json +4 -8
  157. package/skills/lit-pptx/scripts/craft_extras.py +56 -3
  158. package/skills/lit-pptx/scripts/deck_output.py +75 -90
  159. package/skills/lit-pptx/scripts/embed_fonts.py +13 -1
  160. package/skills/lit-pptx/scripts/layout_inventory.py +12 -5
  161. package/skills/lit-pptx/scripts/lib/grid-resolver.js +101 -110
  162. package/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
  163. package/skills/lit-pptx/scripts/lib/render-pack.js +373 -566
  164. package/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
  165. package/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
  166. package/skills/lit-pptx/scripts/qa_deck.py +10 -11
  167. package/skills/lit-pptx/scripts/render_pages.py +118 -0
  168. package/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
  169. package/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
  170. package/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
  171. package/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  172. package/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
  173. package/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
  174. package/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  175. package/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  176. package/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
  177. package/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
  178. package/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  179. package/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
  180. package/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
  181. package/skills/lit-recap/SKILL.md +4 -0
  182. package/skills/lit-schematic/SKILL.md +118 -0
  183. package/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +38 -0
  184. package/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +133 -0
  185. package/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +87 -0
  186. package/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +85 -0
  187. package/skills/lit-schematic/references/cutaway-outputs.md +70 -0
  188. package/skills/lit-schematic/references/isometric-craft.md +101 -0
  189. package/skills/lit-schematic/references/journal-figures.md +72 -0
  190. package/skills/lit-schematic/references/kit-api.md +134 -0
  191. package/skills/lit-schematic/references/pfd-and-layout.md +53 -0
  192. package/skills/lit-schematic/references/pid-drafting.md +103 -0
  193. package/skills/lit-schematic/references/pid-symbols.md +163 -0
  194. package/skills/lit-schematic/references/verification.md +100 -0
  195. package/skills/lit-schematic/scripts/doctor.mjs +57 -0
  196. package/skills/lit-schematic/scripts/lib/figure.mjs +81 -0
  197. package/skills/lit-schematic/scripts/lib/fonts.mjs +25 -0
  198. package/skills/lit-schematic/scripts/lib/geom.mjs +113 -0
  199. package/skills/lit-schematic/scripts/lib/interactive.mjs +292 -0
  200. package/skills/lit-schematic/scripts/lib/isa.mjs +115 -0
  201. package/skills/lit-schematic/scripts/lib/iso.mjs +719 -0
  202. package/skills/lit-schematic/scripts/lib/legend-sheet.mjs +162 -0
  203. package/skills/lit-schematic/scripts/lib/pid.mjs +937 -0
  204. package/skills/lit-schematic/scripts/lib/png.mjs +132 -0
  205. package/skills/lit-schematic/scripts/lib/qa-core.mjs +658 -0
  206. package/skills/lit-schematic/scripts/lib/route.mjs +218 -0
  207. package/skills/lit-schematic/scripts/lib/sheet.mjs +890 -0
  208. package/skills/lit-schematic/scripts/lib/svg.mjs +82 -0
  209. package/skills/lit-schematic/scripts/lib/theme.mjs +115 -0
  210. package/skills/lit-schematic/scripts/motion.mjs +129 -0
  211. package/skills/lit-schematic/scripts/qa.mjs +92 -0
  212. package/skills/lit-schematic/scripts/render.mjs +233 -0
  213. package/skills/lit-schematic/scripts/scaffold.mjs +60 -0
  214. package/skills/lit-scientific-visualization/SKILL.md +27 -4
  215. package/skills/lit-typographic-motion/SKILL.md +19 -17
  216. package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +9 -0
  217. package/skills/lit-typographic-motion/engine/accents.mjs +6 -0
  218. package/skills/lit-typographic-motion/engine/assets.mjs +282 -0
  219. package/skills/lit-typographic-motion/engine/blender/runner.py +228 -0
  220. package/skills/lit-typographic-motion/engine/blender.mjs +126 -0
  221. package/skills/lit-typographic-motion/engine/board.mjs +138 -0
  222. package/skills/lit-typographic-motion/engine/cover/assets.mjs +11 -8
  223. package/skills/lit-typographic-motion/engine/cover/capture.mjs +6 -6
  224. package/skills/lit-typographic-motion/engine/cover/render.mjs +20 -7
  225. package/skills/lit-typographic-motion/engine/cover/treatment.mjs +30 -3
  226. package/skills/lit-typographic-motion/engine/fill.mjs +54 -0
  227. package/skills/lit-typographic-motion/engine/gallery.mjs +118 -0
  228. package/skills/lit-typographic-motion/engine/house.mjs +42 -0
  229. package/skills/lit-typographic-motion/engine/look.mjs +65 -8
  230. package/skills/lit-typographic-motion/engine/loudness.mjs +101 -0
  231. package/skills/lit-typographic-motion/engine/scaffold.mjs +219 -0
  232. package/skills/lit-typographic-motion/engine/score.mjs +594 -0
  233. package/skills/lit-typographic-motion/engine/sound.mjs +9 -57
  234. package/skills/lit-typographic-motion/engine/stage/capture.mjs +23 -9
  235. package/skills/lit-typographic-motion/engine/stage/clock.js +8 -2
  236. package/skills/lit-typographic-motion/engine/stage/cover-init.js +12 -1
  237. package/skills/lit-typographic-motion/engine/stage/frame-worker.mjs +15 -7
  238. package/skills/lit-typographic-motion/engine/stage/gate.mjs +48 -4
  239. package/skills/lit-typographic-motion/engine/stage/qa-page.js +16 -5
  240. package/skills/lit-typographic-motion/engine/stage/qa.mjs +148 -7
  241. package/skills/lit-typographic-motion/engine/stage/render.mjs +69 -7
  242. package/skills/lit-typographic-motion/engine/stage/scan.mjs +20 -1
  243. package/skills/lit-typographic-motion/engine/stage/stage-kit.js +85 -4
  244. package/skills/lit-typographic-motion/engine/stills.mjs +1 -1
  245. package/skills/lit-typographic-motion/engine/styles.mjs +221 -0
  246. package/skills/lit-typographic-motion/engine/text.mjs +1 -1
  247. package/skills/lit-typographic-motion/engine/treatment.mjs +190 -6
  248. package/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  249. package/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  250. package/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  251. package/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  252. package/skills/lit-typographic-motion/gate.mjs +18 -2
  253. package/skills/lit-typographic-motion/probe.mjs +13 -1
  254. package/skills/lit-typographic-motion/recipes/hero-3d/expected.json +64 -0
  255. package/skills/lit-typographic-motion/recipes/hero-3d/index.html +132 -0
  256. package/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +2 -0
  257. package/skills/lit-typographic-motion/recipes/painted-light/expected.json +2 -0
  258. package/skills/lit-typographic-motion/recipes/weight-tide/expected.json +2 -0
  259. package/skills/lit-typographic-motion/references/cover.md +56 -13
  260. package/skills/lit-typographic-motion/references/craft-loop.md +20 -7
  261. package/skills/lit-typographic-motion/references/depth.md +112 -0
  262. package/skills/lit-typographic-motion/references/runtime.md +21 -6
  263. package/skills/lit-typographic-motion/references/sound.md +76 -0
  264. package/skills/lit-typographic-motion/references/stage.md +50 -7
  265. package/skills/lit-typographic-motion/references/studio.md +162 -0
  266. package/skills/lit-typographic-motion/references/style-bibles.md +18 -0
  267. package/skills/lit-typographic-motion/references/treatment.md +108 -9
  268. package/skills/lit-typographic-motion/render.mjs +60 -3
  269. package/skills/lit-typographic-motion/styles/paper.json +185 -0
  270. package/skills/lit-typographic-motion/styles/swiss-signal.json +188 -0
  271. package/skills/lit-typographic-motion/styles/terminalcore.json +182 -0
  272. package/skills/lit-typographic-motion/styles/tidal.json +187 -0
  273. package/skills/litresearch/SKILL.md +2 -0
  274. package/skills/managed-skill-manifest.json +517 -88
  275. package/skills/readme-studio/SKILL.md +1 -1
  276. package/skills/review-work/SKILL.md +2 -0
  277. package/skills/start-work/SKILL.md +8 -0
  278. package/skills/workflow-loop/SKILL.md +4 -0
  279. package/tools/check-pack-payload.mjs +5 -2
  280. package/tools/payload-substance-parity.json +4 -3
  281. package/tools/qa-cover-recipes.mjs +4 -3
  282. package/tools/version-manifests.json +2 -2
@@ -5,6 +5,9 @@
5
5
  // the band always move together.
6
6
  import { ease, mixHex, motionTiming, seg } from "./mission-motion.js";
7
7
  import { contextWarnPercent, currentStepIndex, missionStatus, stepName } from "./mission-model.js";
8
+ import { laneColor, reviewLanes, reviewMark, turnColor, turnMark } from "./mission-looks.js";
9
+ import { isRainbowSkill, rainbowColor, rainbowPill, rainbowText, rewindMark } from "./mission-rainbow.js";
10
+ import { threadLines } from "./mission-threads.js";
8
11
  import { cellWidth, chipSparkCells, chipSparkWidth, crumb, fade, lineWidth, linkColor, liveSegments, missionPanel, progressBar, shimmerAt, shine, stageFlash, statusSegments, truncateCells } from "./mission-text.js";
9
12
  /**
10
13
  * Reads the band switches from the plugin's tui.json options: `band: false` turns the band off,
@@ -34,6 +37,7 @@ export function readNotifyOptions(options) {
34
37
  return { enabled: desktop || sound, desktop, sound };
35
38
  }
36
39
  const minimumText = 12;
40
+ const idleHint = "lit adhd to pick up where you left off";
37
41
  function elapsedText(minutes) {
38
42
  const total = Math.max(0, minutes ?? 0);
39
43
  return total < 60 ? `${total}m` : `${Math.floor(total / 60)}h${String(total % 60).padStart(2, "0")}`;
@@ -129,10 +133,10 @@ function phaseLabel(view, frame, palette, rest) {
129
133
  return { text: `⚑ ${status.text}`, fg: mixHex(palette.info, palette.warning, Math.max(frame.warn, 0.5)), bold: true };
130
134
  return { text: `● ${stepName(view.phase, view).toUpperCase()}`, fg: palette.info, bold: true };
131
135
  }
132
- function topLine(view, frame, palette, width, border, rest) {
136
+ function topLine(view, frame, palette, width, border, rest, mark) {
133
137
  const brand = [{ text: "🔥 ", fg: palette.text }, { text: "LIT", fg: shine(palette.secondary, palette, 0.5, shimmerAt(frame), 0.6), bold: true }];
134
138
  const name = view.active ? view.discipline : "ready";
135
- const right = [phaseLabel(view, frame, palette, rest)];
139
+ const right = mark ?? [phaseLabel(view, frame, palette, rest)];
136
140
  const withName = (room) => [...brand, { text: " · ", fg: palette.muted }, { text: truncateCells(name, room), fg: view.active ? palette.text : palette.muted }];
137
141
  const nameRoom = width - cellWidth("╭─") - lineWidth(withName(0)) - 2 - (lineWidth(right) + 2) - cellWidth("─╮") - 1;
138
142
  const attempts = [];
@@ -149,6 +153,8 @@ function topLine(view, frame, palette, width, border, rest) {
149
153
  /** The figures in the bottom border, most important first; the last ones go first when room runs out. */
150
154
  function figureParts(view, figures, palette) {
151
155
  const parts = [];
156
+ if (figures.restartTo !== undefined)
157
+ parts.push({ text: `restart to ${truncateCells(figures.restartTo, 16)}`, fg: palette.warning });
152
158
  if (figures.contextPercent !== null) {
153
159
  const percent = Math.round(figures.contextPercent);
154
160
  parts.push({ text: `ctx ${percent}%`, fg: percent >= contextWarnPercent ? palette.warning : palette.muted });
@@ -190,12 +196,16 @@ function trackPills(view, frame, palette, tight) {
190
196
  return out;
191
197
  }
192
198
  /** The text after the track in an idle session: the project's last goal, or how to start. */
193
- function idleText(last, palette, room) {
194
- if (last === undefined || last.title === "")
195
- return [{ text: " ", fg: palette.muted }, { text: truncateCells("add lit to a prompt to start", room - 2), fg: palette.muted }];
199
+ function idleText(last, palette, room, earlier) {
200
+ if (last === undefined || last.title === "") {
201
+ return [{ text: " ", fg: palette.muted }, { text: truncateCells(earlier ? idleHint : "add lit to a prompt to start", room - 2), fg: earlier ? palette.accent : palette.muted }];
202
+ }
196
203
  const mark = last.complete ? { text: ` ✓ ${last.passed}/${last.total}`, fg: palette.success } : { text: ` ● ${last.passed}/${last.total}`, fg: palette.info };
197
204
  const head = { text: " last: ", fg: palette.muted };
198
- return [head, { text: truncateCells(last.title, room - cellWidth(head.text) - cellWidth(mark.text)), fg: palette.text }, mark];
205
+ const hint = earlier ? [{ text: ` ${idleHint}`, fg: palette.accent }] : [];
206
+ const fixed = cellWidth(head.text) + cellWidth(mark.text);
207
+ const withHint = hint.length > 0 && room - fixed - lineWidth(hint) >= 12 ? hint : [];
208
+ return [head, { text: truncateCells(last.title, room - fixed - lineWidth(withHint)), fg: palette.text }, mark, ...withHint];
199
209
  }
200
210
  // Shedding order as the row narrows: the next-task text, bar width, the bar, the criteria count and
201
211
  // then the step labels. Each plan is tried in turn and the first that fits wins.
@@ -207,7 +217,7 @@ const trackPlans = [
207
217
  { tight: false, bar: 0, criteria: false, text: false },
208
218
  { tight: true, bar: 0, criteria: false, text: false }
209
219
  ];
210
- function trackRow(view, frame, palette, width, last) {
220
+ function trackRow(view, frame, palette, width, last, earlier) {
211
221
  const sweep = shimmerAt(frame);
212
222
  const gap = { text: " ", fg: palette.muted };
213
223
  for (const plan of trackPlans) {
@@ -220,7 +230,7 @@ function trackRow(view, frame, palette, width, last) {
220
230
  if (fixed > width)
221
231
  continue;
222
232
  const room = width - fixed;
223
- const after = plan.text ? (view.active ? trailing(view, frame, palette, room) : idleText(last, palette, room)) : [];
233
+ const after = plan.text ? (view.active ? trailing(view, frame, palette, room) : idleText(last, palette, room, earlier)) : [];
224
234
  const keepText = plan.text && after.length > 0 && (view.complete ? lineWidth(after) <= room : room >= minimumText);
225
235
  if (plan.text && after.length > 0 && !keepText)
226
236
  continue;
@@ -231,8 +241,10 @@ function trackRow(view, frame, palette, width, last) {
231
241
  }
232
242
  return fitLine(trackPills(view, frame, palette, true), width, palette);
233
243
  }
234
- function chipSegments(chip, shimmer, palette) {
244
+ function chipSegments(chip, shimmer, palette, rainbow) {
235
245
  const text = ` ${chip.name} `;
246
+ if (rainbow !== undefined && chip.current && isRainbowSkill(chip.name))
247
+ return rainbowPill(text, rainbow, 180);
236
248
  if (!chip.current)
237
249
  return [{ text, fg: palette.muted, ...(palette.element === undefined ? {} : { bg: palette.element }) }];
238
250
  const sweep = shimmer?.names.includes(chip.name) === true ? shimmer.sweep : -1;
@@ -245,11 +257,11 @@ function chipSegments(chip, shimmer, palette) {
245
257
  * The skill chips row, exactly `width` cells: bright LIT accent pills for the current request first, then
246
258
  * dim recent names, and `+N` for the ones that do not fit. The label goes before any chip does.
247
259
  */
248
- export function skillRow(chips, shimmer, palette, width) {
260
+ export function skillRow(chips, shimmer, palette, width, rainbow) {
249
261
  const label = { text: "skills ", fg: palette.muted };
250
262
  if (chips.length === 0)
251
263
  return fitLine([label, { text: " none yet", fg: palette.muted }], width, palette);
252
- const pills = chips.map((chip) => chipSegments(chip, shimmer, palette));
264
+ const pills = chips.map((chip) => chipSegments(chip, shimmer, palette, rainbow));
253
265
  const space = { text: " ", fg: palette.muted };
254
266
  for (const lead of [[label], []]) {
255
267
  const all = lineWidth(lead) + pills.reduce((total, pill, index) => total + lineWidth(pill) + (index === 0 ? 0 : 1), 0);
@@ -275,6 +287,24 @@ export function skillRow(chips, shimmer, palette, width) {
275
287
  }
276
288
  return fitLine([{ text: `+${pills.length}`, fg: palette.muted }], width, palette);
277
289
  }
290
+ /** The longest run of rule in the top border, split into the five review lanes, each in its lane's colour. */
291
+ function laneRule(top, reports, palette) {
292
+ let at = -1;
293
+ top.forEach((segment, index) => {
294
+ if (/^─+$/u.test(segment.text) && (at < 0 || segment.text.length > (top[at]?.text.length ?? 0)))
295
+ at = index;
296
+ });
297
+ const rule = top[at];
298
+ if (rule === undefined || rule.text.length < reviewLanes.length)
299
+ return top;
300
+ const size = rule.text.length;
301
+ const parts = reviewLanes.map((lane, index) => {
302
+ const from = Math.floor((index * size) / reviewLanes.length);
303
+ const to = Math.floor(((index + 1) * size) / reviewLanes.length);
304
+ return { text: "─".repeat(to - from), fg: laneColor(lane, reports, palette), ...(reports[lane] === undefined ? {} : { bold: true }) };
305
+ });
306
+ return [...top.slice(0, at), ...parts, ...top.slice(at + 1)];
307
+ }
278
308
  /**
279
309
  * The status card: four rows of exactly `width` cells whether the session is idle or working, so
280
310
  * the prompt never moves. Each row sheds its own low-priority parts as the terminal narrows.
@@ -284,14 +314,43 @@ export function statusCard(input, palette, width) {
284
314
  const rest = resting(input.view, input.busy);
285
315
  // At rest the track keeps what is done and unlights the step that was running.
286
316
  const view = rest ? { ...input.view, steps: input.view.steps.map((step) => (step.state === "now" ? { ...step, state: "todo" } : step)) } : input.view;
287
- const border = borderColor(view, frame, palette, rest);
317
+ // One look at a time: waiting on the user first, then the rewind look, then the review lanes.
318
+ const turn = input.turn;
319
+ const rainbow = turn === undefined ? input.rainbow : undefined;
320
+ const lanes = turn === undefined && rainbow === undefined ? input.lanes : undefined;
321
+ const border = turn !== undefined ? turnColor(palette, turn.glow) : rainbow === undefined ? borderColor(view, frame, palette, rest) : rainbowColor(rainbow.hue);
322
+ const trackPalette = rainbow === undefined ? palette : { ...palette, primary: rainbowColor(rainbow.hue + 40), accent: rainbowColor(rainbow.hue + 160) };
288
323
  const inner = width - 4;
289
324
  const row = (content) => (inner < 0 ? fitLine([], width, palette) : [{ text: "│ ", fg: border }, ...fitLine(content, inner, palette), { text: " │", fg: border }]);
325
+ const mark = turn !== undefined ? turnMark(palette, turn.glow) : rainbow !== undefined ? rewindMark(rainbow) : lanes !== undefined ? reviewMark(lanes, palette) : undefined;
326
+ const ruledTop = topLine(view, frame, palette, width, border, rest, mark);
327
+ const top = lanes === undefined ? ruledTop : laneRule(ruledTop, lanes, palette);
328
+ const threadRows = input.threads === undefined ? [] : threadLines(input.threads, palette, Math.max(0, inner), {
329
+ now: frame.now ?? 0,
330
+ motion: input.motion ?? true,
331
+ ...(rainbow === undefined ? {} : { gaugeColor: (index) => rainbowColor(rainbow.hue + 60 + index * 50) })
332
+ });
333
+ while (threadRows.length < (input.reserve ?? 0))
334
+ threadRows.push([]);
335
+ const track = trackRow(view, frame, trackPalette, Math.max(0, inner), input.last, input.earlier === true);
336
+ const bottom = bottomLine(view, input.figures, palette, width, border);
337
+ if (rainbow === undefined)
338
+ return [top, row(track), row(skillRow(input.chips, input.shimmer, palette, Math.max(0, inner))), ...threadRows.map(row), bottom];
339
+ // The rewind look reaches every pill cell by cell: LIT, the running phase, the lit-adhd chip; the phase
340
+ // labels and the context figure turn on the card's own ground.
341
+ const names = view.steps.map((step) => stepName(step.id, view).toLowerCase());
342
+ const label = (text) => names.findIndex((name) => text.trim().toLowerCase().startsWith(name));
290
343
  return [
291
- topLine(view, frame, palette, width, border, rest),
292
- row(trackRow(view, frame, palette, Math.max(0, inner), input.last)),
293
- row(skillRow(input.chips, input.shimmer, palette, Math.max(0, inner))),
294
- bottomLine(view, input.figures, palette, width, border)
344
+ top.flatMap((segment) => (segment.text === "LIT" ? rainbowPill("LIT", rainbow, 90) : [segment])),
345
+ row(track.flatMap((segment) => {
346
+ if (segment.bg !== undefined && segment.bold === true && /\S/u.test(segment.text))
347
+ return rainbowPill(segment.text, rainbow, 160);
348
+ const at = segment.bg === undefined ? label(segment.text) : -1;
349
+ return at >= 0 ? [{ ...segment, fg: rainbowColor(rainbow.hue + 200 + at * 50), bold: true }] : [segment];
350
+ })),
351
+ row(skillRow(input.chips, input.shimmer, palette, Math.max(0, inner), rainbow)),
352
+ ...threadRows.map(row),
353
+ bottom.flatMap((segment) => (segment.text.startsWith("ctx ") ? rainbowText(segment.text, rainbow, 120) : [segment]))
295
354
  ];
296
355
  }
297
356
  const cardRows = 4;
@@ -0,0 +1,38 @@
1
+ import type { MissionPalette, Segment } from "./mission-text.ts";
2
+ import type { MissionView } from "./mission-model.ts";
3
+ import type { SkillChip } from "./mission-skills.ts";
4
+ export type SkillLook = "rewind" | "review";
5
+ /** The look a skill name carries, if any. */
6
+ export declare function lookOfSkill(name: string): SkillLook | undefined;
7
+ /** The skill look while a turn runs: the session's discipline first, then a chip this turn lit. */
8
+ export declare function activeSkillLook(view: Pick<MissionView, "active" | "discipline">, chips: readonly SkillChip[], busy: boolean | undefined): SkillLook | undefined;
9
+ /** What the session is waiting on the user for, read from records; every field is a real signal. */
10
+ export type WaitSignals = {
11
+ /** OpenCode holds a permission request or a question for this session. */
12
+ readonly asked: boolean;
13
+ /** The last message is the user's: they have answered, so nothing waits on them now. */
14
+ readonly userSpokeLast: boolean;
15
+ /** The last reply of the turn opens with `BLOCKED:`. */
16
+ readonly blockedReply: boolean;
17
+ /** A typed start-work run is paused on a boundary only the user can approve. */
18
+ readonly boundary: boolean;
19
+ };
20
+ /** True while the session waits on the user: asked now, or idle on a decision, a BLOCKED reply or a boundary. */
21
+ export declare function waitingOnYou(view: Pick<MissionView, "active" | "warnings">, busy: boolean | undefined, signals: WaitSignals): boolean;
22
+ export declare const turnBreathMs = 3000;
23
+ /** How far the amber has eased toward the border at `now`, 0 to 1; a 3 s sine, 0 under reduced motion. */
24
+ export declare function turnGlow(now: number, motion: boolean): number;
25
+ /** The amber of the waiting look: the warning colour eased at most 25 % toward the card's border. */
26
+ export declare function turnColor(palette: MissionPalette, glow: number): string;
27
+ export declare function turnMark(palette: MissionPalette, glow: number): Segment[];
28
+ export declare const reviewLanes: readonly ["scope", "tests", "package", "security", "surface"];
29
+ export type ReviewLane = (typeof reviewLanes)[number];
30
+ export type LaneResult = "pass" | "fail" | "blocked";
31
+ export type LaneReports = Partial<Readonly<Record<ReviewLane, LaneResult>>>;
32
+ export declare const laneResults: readonly LaneResult[];
33
+ export declare function isReviewLane(value: unknown): value is ReviewLane;
34
+ export declare function isLaneResult(value: unknown): value is LaneResult;
35
+ /** One lane's colour on the border: its own hue once it reports a pass, error once it fails, dim until then. */
36
+ export declare function laneColor(lane: ReviewLane, reports: LaneReports, palette: MissionPalette): string;
37
+ /** The corner label of the review look: how many of the five lanes have reported, never a guess. */
38
+ export declare function reviewMark(reports: LaneReports, palette: MissionPalette): Segment[];
@@ -0,0 +1,73 @@
1
+ // The looks the status card can wear, chosen in one place. A skill look belongs to the discipline at work
2
+ // or to a chip lit by this turn: lit-adhd and its alias lit-rewind wear the rewind look, review-work wears
3
+ // its five lanes. A state look belongs to the session: `YOUR TURN` while it waits on the user, and the
4
+ // done burst (the completion sparks of mission-motion) when a goal reaches done. Each look reads only
5
+ // records OpenCode or LitOpenCode already keep, so a lane without a report stays dim and nothing is
6
+ // guessed. Motion is slow and small: the amber breath is one hue held while its mix eases over 3 s.
7
+ import { mixHex } from "./mission-motion.js";
8
+ const skillLooks = Object.freeze({ "lit-adhd": "rewind", "lit-rewind": "rewind", "review-work": "review" });
9
+ /** The look a skill name carries, if any. */
10
+ export function lookOfSkill(name) {
11
+ return Object.hasOwn(skillLooks, name) ? skillLooks[name] : undefined;
12
+ }
13
+ /** The skill look while a turn runs: the session's discipline first, then a chip this turn lit. */
14
+ export function activeSkillLook(view, chips, busy) {
15
+ if (busy === false)
16
+ return undefined;
17
+ const own = view.active ? lookOfSkill(view.discipline) : undefined;
18
+ if (own !== undefined)
19
+ return own;
20
+ for (const chip of chips) {
21
+ const look = chip.current ? lookOfSkill(chip.name) : undefined;
22
+ if (look !== undefined)
23
+ return look;
24
+ }
25
+ return undefined;
26
+ }
27
+ /** True while the session waits on the user: asked now, or idle on a decision, a BLOCKED reply or a boundary. */
28
+ export function waitingOnYou(view, busy, signals) {
29
+ if (signals.asked)
30
+ return true;
31
+ if (signals.userSpokeLast || busy === true)
32
+ return false;
33
+ const decision = view.active && view.warnings.some((warning) => warning.kind === "decision");
34
+ return decision || signals.blockedReply || signals.boundary;
35
+ }
36
+ export const turnBreathMs = 3000;
37
+ /** How far the amber has eased toward the border at `now`, 0 to 1; a 3 s sine, 0 under reduced motion. */
38
+ export function turnGlow(now, motion) {
39
+ if (!motion)
40
+ return 0;
41
+ return (1 - Math.cos((2 * Math.PI * (now % turnBreathMs)) / turnBreathMs)) / 2;
42
+ }
43
+ /** The amber of the waiting look: the warning colour eased at most 25 % toward the card's border. */
44
+ export function turnColor(palette, glow) {
45
+ return mixHex(palette.warning, palette.borderActive, Math.min(1, Math.max(0, glow)) * 0.25);
46
+ }
47
+ export function turnMark(palette, glow) {
48
+ return [{ text: "◆ YOUR TURN", fg: turnColor(palette, glow), bold: true }];
49
+ }
50
+ export const reviewLanes = ["scope", "tests", "package", "security", "surface"];
51
+ export const laneResults = ["pass", "fail", "blocked"];
52
+ export function isReviewLane(value) {
53
+ return typeof value === "string" && reviewLanes.includes(value);
54
+ }
55
+ export function isLaneResult(value) {
56
+ return typeof value === "string" && laneResults.includes(value);
57
+ }
58
+ /** One lane's colour on the border: its own hue once it reports a pass, error once it fails, dim until then. */
59
+ export function laneColor(lane, reports, palette) {
60
+ const result = reports[lane];
61
+ if (result === undefined)
62
+ return palette.borderSubtle;
63
+ if (result !== "pass")
64
+ return palette.error;
65
+ const hues = [palette.primary, palette.info, palette.accent, palette.secondary, palette.success];
66
+ return hues[reviewLanes.indexOf(lane)] ?? palette.success;
67
+ }
68
+ /** The corner label of the review look: how many of the five lanes have reported, never a guess. */
69
+ export function reviewMark(reports, palette) {
70
+ const reported = reviewLanes.filter((lane) => reports[lane] !== undefined).length;
71
+ const failed = reviewLanes.some((lane) => reports[lane] !== undefined && reports[lane] !== "pass");
72
+ return [{ text: `◇ REVIEW ${reported}/${reviewLanes.length}`, fg: failed ? palette.error : palette.info, bold: true }];
73
+ }
@@ -20,6 +20,7 @@ export type WorkInput = {
20
20
  readonly status: string;
21
21
  readonly iterations: number;
22
22
  readonly plan?: PlanChecklist;
23
+ readonly boundary?: boolean;
23
24
  };
24
25
  export type ResearchStage = "decompose" | "wave" | "expand" | "verify" | "synthesis";
25
26
  /** The latest litresearch run folder: what its journal, claim graph and report say so far. */
@@ -143,6 +144,7 @@ export declare function readWorkState(raw: unknown, sessionID: string): {
143
144
  readonly status: string;
144
145
  readonly iterations: number;
145
146
  readonly planPath: string;
147
+ readonly boundary?: true;
146
148
  } | undefined;
147
149
  type LedgerCriterion = {
148
150
  readonly id: string;
@@ -109,7 +109,9 @@ export function readWorkState(raw, sessionID) {
109
109
  return undefined;
110
110
  const history = Array.isArray(raw.history) ? raw.history.filter(isRecord) : [];
111
111
  const plan = isRecord(raw.plan) ? text(raw.plan.path) : "";
112
- return { status, iterations: history.filter((entry) => entry.type === "work.progressed").length, planPath: plan };
112
+ // A paused run holding a boundary waits for the user's typed approval.
113
+ const boundary = status === "paused" && isRecord(raw.pendingBoundary);
114
+ return { status, iterations: history.filter((entry) => entry.type === "work.progressed").length, planPath: plan, ...(boundary ? { boundary: true } : {}) };
113
115
  }
114
116
  // The ledger CLI accepts `passed`, `failed` and `completed` as spellings of its own statuses; a goals.json
115
117
  // the agent writes by hand also marks a finished criterion `completed` or `done`.
@@ -0,0 +1,33 @@
1
+ import type { SkillChip } from "./mission-skills.ts";
2
+ import type { Line, Segment } from "./mission-text.ts";
3
+ import type { MissionView } from "./mission-model.ts";
4
+ export declare const rainbowTickMs = 250;
5
+ export type Rainbow = {
6
+ readonly hue: number;
7
+ };
8
+ /** True while the session's current discipline or a chip lit by this turn is lit-adhd or lit-rewind. */
9
+ export declare function rainbowActive(view: Pick<MissionView, "active" | "discipline">, chips: readonly SkillChip[], busy: boolean | undefined): boolean;
10
+ /** The rewind look outlasts its turn so the band stays lit while the user reads the brief. */
11
+ export declare const rainbowLingerMs: number;
12
+ /** Shown while lit-adhd runs, then until the next prompt, for at most rainbowLingerMs after it was last seen. */
13
+ export declare function rainbowShows(active: boolean, seenAt: number | undefined, lastPromptAt: number | undefined, now: number): boolean;
14
+ /** The top-border mark of the rewind look: REWIND « with a hue per letter, then a four-cell sparkle trail. */
15
+ export declare function rewindMark(rainbow: Rainbow): Line;
16
+ /** True for the skill names that carry the rewind look. */
17
+ export declare const isRainbowSkill: (name: string) => boolean;
18
+ /** A pill cell's fill at this hue: the rainbow colour lifted toward white, so the dark ink keeps 4.5:1. */
19
+ export declare function rainbowCell(hue: number): string;
20
+ /**
21
+ * A pill drawn cell by cell through the rainbow: each cell a step further round the wheel, dark bold ink
22
+ * on top. Only the colours of fixed cells move, so the text and width never change and nothing flashes.
23
+ */
24
+ export declare function rainbowPill(text: string, rainbow: Rainbow, offset: number): Segment[];
25
+ /** Plain text with each character a step round the wheel, for labels that sit on the card's ground. */
26
+ export declare function rainbowText(text: string, rainbow: Rainbow, offset: number): Segment[];
27
+ /** Whether the rainbow may play at all: tui.json motion is not off and NO_COLOR is unset or empty. */
28
+ export declare function rainbowAllowed(motion: boolean, noColor: string | undefined): boolean;
29
+ export declare function rainbowAt(now: number): Rainbow;
30
+ /** An OKLCH colour at the rainbow's fixed lightness and chroma, as `#rrggbb`. */
31
+ export declare function rainbowColor(hue: number): string;
32
+ /** WCAG relative luminance of a `#rrggbb` colour, 0 to 1. */
33
+ export declare function relativeLuminance(hex: string): number;
@@ -0,0 +1,90 @@
1
+ // The look of the band while lit-adhd (alias lit-rewind) is the discipline at work: the border, the LIT
2
+ // word and the gauges take a colour that walks round the hue wheel. Every colour is drawn at one fixed
3
+ // OKLCH lightness, so the brightness of the band never swings; only the hue moves, a few degrees per
4
+ // 250 ms tick, which is four small colour steps a second and no flash at all. It is off under reduced
5
+ // motion (tui.json `motion: false`) and NO_COLOR, and it never reaches a session that is not rewinding.
6
+ import { activeSkillLook, lookOfSkill } from "./mission-looks.js";
7
+ import { mixHex } from "./mission-motion.js";
8
+ export const rainbowTickMs = 250;
9
+ /** Degrees of hue per second: a full turn every 12 s, 7.5 degrees per tick. */
10
+ const degreesPerSecond = 30;
11
+ const lightness = 0.8;
12
+ const chroma = 0.115;
13
+ /** True while the session's current discipline or a chip lit by this turn is lit-adhd or lit-rewind. */
14
+ export function rainbowActive(view, chips, busy) {
15
+ return activeSkillLook(view, chips, busy) === "rewind";
16
+ }
17
+ /** The rewind look outlasts its turn so the band stays lit while the user reads the brief. */
18
+ export const rainbowLingerMs = 15 * 60_000;
19
+ /** Shown while lit-adhd runs, then until the next prompt, for at most rainbowLingerMs after it was last seen. */
20
+ export function rainbowShows(active, seenAt, lastPromptAt, now) {
21
+ if (active)
22
+ return true;
23
+ if (seenAt === undefined || now - seenAt > rainbowLingerMs)
24
+ return false;
25
+ return lastPromptAt === undefined || lastPromptAt <= seenAt;
26
+ }
27
+ const sparkleFrames = ["✦·⋆·", "·✦·⋆", "⋆·✦·", "·⋆·✧"];
28
+ /** The top-border mark of the rewind look: REWIND « with a hue per letter, then a four-cell sparkle trail. */
29
+ export function rewindMark(rainbow) {
30
+ const letters = [..."REWIND"].map((letter, index) => ({ text: letter, fg: rainbowColor(rainbow.hue + index * 45), bold: true }));
31
+ // The hue advances one step per tick, so it also clocks the trail.
32
+ const tick = Math.floor(rainbow.hue / ((degreesPerSecond * rainbowTickMs) / 1000));
33
+ const trail = sparkleFrames[tick % sparkleFrames.length] ?? sparkleFrames[0];
34
+ return [...letters, { text: " «", fg: rainbowColor(rainbow.hue + 270) }, { text: " ", fg: rainbowColor(rainbow.hue) }, ...[...trail].map((glyph, index) => ({ text: glyph, fg: rainbowColor(rainbow.hue + 300 + index * 20) }))];
35
+ }
36
+ /** True for the skill names that carry the rewind look. */
37
+ export const isRainbowSkill = (name) => lookOfSkill(name) === "rewind";
38
+ /** Degrees of hue one pill spans from its first cell to its last. */
39
+ const pillSpread = 126;
40
+ const pillInk = "#14161c";
41
+ /** A pill cell's fill at this hue: the rainbow colour lifted toward white, so the dark ink keeps 4.5:1. */
42
+ export function rainbowCell(hue) {
43
+ return mixHex(rainbowColor(hue), "#ffffff", 0.3);
44
+ }
45
+ /**
46
+ * A pill drawn cell by cell through the rainbow: each cell a step further round the wheel, dark bold ink
47
+ * on top. Only the colours of fixed cells move, so the text and width never change and nothing flashes.
48
+ */
49
+ export function rainbowPill(text, rainbow, offset) {
50
+ const chars = [...text];
51
+ const step = chars.length <= 1 ? 0 : pillSpread / (chars.length - 1);
52
+ return chars.map((char, index) => ({ text: char, fg: pillInk, bg: rainbowCell(rainbow.hue + offset + index * step), bold: true }));
53
+ }
54
+ /** Plain text with each character a step round the wheel, for labels that sit on the card's ground. */
55
+ export function rainbowText(text, rainbow, offset) {
56
+ return [...text].map((char, index) => ({ text: char, fg: rainbowColor(rainbow.hue + offset + index * 24), bold: true }));
57
+ }
58
+ /** Whether the rainbow may play at all: tui.json motion is not off and NO_COLOR is unset or empty. */
59
+ export function rainbowAllowed(motion, noColor) {
60
+ return motion && (noColor === undefined || noColor === "");
61
+ }
62
+ export function rainbowAt(now) {
63
+ return { hue: ((now / 1000) * degreesPerSecond) % 360 };
64
+ }
65
+ function encode(linear) {
66
+ const clamped = Math.min(1, Math.max(0, linear));
67
+ return clamped <= 0.0031308 ? 12.92 * clamped : 1.055 * clamped ** (1 / 2.4) - 0.055;
68
+ }
69
+ /** An OKLCH colour at the rainbow's fixed lightness and chroma, as `#rrggbb`. */
70
+ export function rainbowColor(hue) {
71
+ const radians = (((hue % 360) + 360) % 360) * (Math.PI / 180);
72
+ const a = chroma * Math.cos(radians);
73
+ const b = chroma * Math.sin(radians);
74
+ const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
75
+ const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
76
+ const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
77
+ const red = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
78
+ const green = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
79
+ const blue = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
80
+ return "#" + [red, green, blue].map((channel) => Math.round(encode(channel) * 255).toString(16).padStart(2, "0")).join("");
81
+ }
82
+ function linear(channel) {
83
+ const value = channel / 255;
84
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
85
+ }
86
+ /** WCAG relative luminance of a `#rrggbb` colour, 0 to 1. */
87
+ export function relativeLuminance(hex) {
88
+ const channels = [1, 3, 5].map((index) => linear(Number.parseInt(hex.slice(index, index + 2), 16)));
89
+ return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
90
+ }
@@ -1,5 +1,7 @@
1
1
  import { type LiveStep, type MissionInput, type ResearchInput, type TodoInput, type WorkInput } from "./mission-model.ts";
2
+ import { type LaneReports, type ReviewLane, type LaneResult } from "./mission-looks.ts";
2
3
  import { type SkillChip } from "./mission-skills.ts";
4
+ import { type ThreadRow, type ThreadSource } from "./mission-threads.ts";
3
5
  type MessageLike = {
4
6
  readonly id: string;
5
7
  readonly role: string;
@@ -88,11 +90,58 @@ export type CardInput = {
88
90
  readonly busy: boolean;
89
91
  readonly contextPercent: number | null;
90
92
  readonly branch?: string;
93
+ /** Two or more lanes of work, from the stack, `task` calls and sibling sessions. */
94
+ readonly threads?: readonly ThreadRow[];
95
+ /** Another session here moved lately, so an idle card can name `lit adhd`. */
96
+ readonly earlier?: boolean;
97
+ /** The lanes review-work has reported since its latest start; present once a review ran here. */
98
+ readonly lanes?: LaneReports;
99
+ /** OpenCode holds a permission request or a question for this session. */
100
+ readonly asked?: boolean;
101
+ /** The last message is the user's. */
102
+ readonly userSpokeLast?: boolean;
103
+ /** The last reply opens with `BLOCKED:`. */
104
+ readonly blockedReply?: boolean;
91
105
  };
106
+ /** A completed `review-work` call with action `lane`: one lane's reported result. */
107
+ export declare function readLaneReport(part: unknown): {
108
+ readonly lane: ReviewLane;
109
+ readonly result: LaneResult;
110
+ } | undefined;
111
+ /**
112
+ * True when the reply's first line of prose opens with `BLOCKED:`. The ignition mark comes first on the
113
+ * host (a fenced LIT block from experimental.text.complete, and the model's `LIT IGNITED` line), so those
114
+ * lines and blank ones are passed over; anything else ends the search.
115
+ */
116
+ export declare function opensBlocked(parts: readonly unknown[]): boolean;
92
117
  /**
93
118
  * What the band card shows beside the mission: the session's skill chips, whether a turn is running,
94
119
  * context use and git branch. OpenCode keeps each session's last `session.status` ({ type: "busy" },
95
120
  * "retry" or "idle"); a session it has not seen run has none, which counts as not running.
96
121
  */
97
122
  export declare function readCardInput(state: MissionStateSource, sessionID: string): CardInput;
123
+ export declare const earlierWorkWindowMs: number;
124
+ /**
125
+ * The lanes beside this session's own mission: its workflow stack frames, its running `task` calls and
126
+ * the stacks of other sessions in the workspace that moved in the last fifteen minutes, read with their
127
+ * liveness records. Another session's completed start-work run shows as done. Only counts that are real
128
+ * totals are carried; the input of a tool call is read for its description alone.
129
+ */
130
+ export declare function readThreadSources(state: MissionStateSource, sessionID: string, root: string | undefined, now: number): ThreadSource[];
131
+ /**
132
+ * Whether earlier work exists for the band's idle hint and the one suggestion: another session here
133
+ * touched its workflow stack within `withinMs`.
134
+ */
135
+ /** When the session's last message and its last user prompt were created, from the reactive store. */
136
+ export declare function messageTimes(state: MissionStateSource, sessionID: string): {
137
+ readonly lastAt?: number;
138
+ readonly lastPromptAt?: number;
139
+ };
140
+ /** The way back in is offered after this long without a message in the session. */
141
+ export declare const adhdBreakMs: number;
142
+ /** The idle card names lit adhd in a fresh session or after a break, only when earlier work exists. */
143
+ export declare function wayBackDue(lastActivityAt: number | undefined, now: number, earlier: boolean): boolean;
144
+ /** At most one start suggestion per workspace per local day; records the day when it answers true. */
145
+ export declare function adhdSuggestDue(days: Map<string, string>, root: string, now: number): boolean;
146
+ export declare function hasEarlierWork(root: string | undefined, sessionID: string, now: number, withinMs: number): boolean;
98
147
  export {};