@erclx/canon 5.25.0 → 5.27.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 (315) hide show
  1. package/README.md +4 -3
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +2 -0
  4. package/claude/skills/auto-ship/SKILL.md +3 -11
  5. package/claude/skills/auto-ship/references/review-findings.md +20 -0
  6. package/claude/skills/backlog-triage/SKILL.md +2 -0
  7. package/claude/skills/bash-cli-script/SKILL.md +2 -0
  8. package/claude/skills/build-in-slices/SKILL.md +2 -0
  9. package/claude/skills/canon-cli/SKILL.md +2 -0
  10. package/claude/skills/canon-feedback/SKILL.md +2 -0
  11. package/claude/skills/canon-feedback-triage/SKILL.md +2 -0
  12. package/claude/skills/canon-operator/SKILL.md +2 -0
  13. package/claude/skills/canon-rollout/SKILL.md +3 -11
  14. package/claude/skills/canon-rollout/references/failure-recovery.md +20 -0
  15. package/claude/skills/canvas/SKILL.md +14 -1
  16. package/claude/skills/ci-workflow/SKILL.md +2 -0
  17. package/claude/skills/code-craft/SKILL.md +2 -0
  18. package/claude/skills/codebase-layout/SKILL.md +2 -0
  19. package/claude/skills/context-fold/REQUIREMENT.md +6 -2
  20. package/claude/skills/context-fold/SKILL.md +9 -21
  21. package/claude/skills/context-fold/references/anchor-sweep.md +2 -2
  22. package/claude/skills/context-fold/references/completion-report.md +26 -0
  23. package/claude/skills/create-rule/SKILL.md +2 -0
  24. package/claude/skills/create-skill/SKILL.md +2 -0
  25. package/claude/skills/create-standard/SKILL.md +2 -0
  26. package/claude/skills/decision-escalate/SKILL.md +2 -0
  27. package/claude/skills/deploy-app/SKILL.md +2 -0
  28. package/claude/skills/deprecation-migration/SKILL.md +2 -0
  29. package/claude/skills/design-extract/SKILL.md +2 -0
  30. package/claude/skills/design-taste/REQUIREMENT.md +1 -1
  31. package/claude/skills/design-taste/SKILL.md +6 -2
  32. package/claude/skills/design-taste/references/adopted.md +40 -0
  33. package/claude/skills/design-taste/references/kinds.md +1 -1
  34. package/claude/skills/design-taste/references/systems.md +1 -7
  35. package/claude/skills/design-taste/references/tells.md +2 -24
  36. package/claude/skills/docs-sync/REQUIREMENT.md +2 -1
  37. package/claude/skills/docs-sync/SKILL.md +2 -0
  38. package/claude/skills/document-health/REQUIREMENT.md +2 -0
  39. package/claude/skills/document-health/SKILL.md +23 -0
  40. package/claude/skills/document-health/references/axes.md +12 -0
  41. package/claude/skills/draft-and-pick/SKILL.md +3 -1
  42. package/claude/skills/draft-doc/SKILL.md +2 -0
  43. package/claude/skills/draft-doc/references/wiki.md +1 -1
  44. package/claude/skills/draft-figure/SKILL.md +2 -0
  45. package/claude/skills/draft-identity/SKILL.md +3 -1
  46. package/claude/skills/draft-ready/SKILL.md +2 -0
  47. package/claude/skills/draft-screencast/SKILL.md +11 -7
  48. package/claude/skills/draft-slides/REQUIREMENT.md +17 -12
  49. package/claude/skills/draft-slides/SKILL.md +33 -20
  50. package/claude/skills/git-branch/SKILL.md +2 -0
  51. package/claude/skills/git-commit/SKILL.md +2 -0
  52. package/claude/skills/git-followup/SKILL.md +2 -0
  53. package/claude/skills/git-issue/SKILL.md +2 -0
  54. package/claude/skills/git-issue/references/issue.md +10 -0
  55. package/claude/skills/git-pr/SKILL.md +2 -0
  56. package/claude/skills/git-pr/references/evidence.md +14 -6
  57. package/claude/skills/git-pr/references/labels.md +12 -0
  58. package/claude/skills/git-ship/SKILL.md +2 -0
  59. package/claude/skills/git-split/SKILL.md +21 -8
  60. package/claude/skills/git-stage/SKILL.md +2 -0
  61. package/claude/skills/git-worktree/SKILL.md +2 -0
  62. package/claude/skills/index-lookup/SKILL.md +2 -0
  63. package/claude/skills/markdown-propose/SKILL.md +2 -0
  64. package/claude/skills/markdown-propose/references/format.md +9 -0
  65. package/claude/skills/memory-capture/SKILL.md +2 -0
  66. package/claude/skills/memory-review/SKILL.md +2 -0
  67. package/claude/skills/memory-review/references/later-phases.md +7 -0
  68. package/claude/skills/plan-feature/SKILL.md +2 -0
  69. package/claude/skills/plan-groundwork/SKILL.md +2 -0
  70. package/claude/skills/plan-intake/SKILL.md +2 -0
  71. package/claude/skills/plan-intake-answer/SKILL.md +2 -0
  72. package/claude/skills/project-commands/SKILL.md +2 -0
  73. package/claude/skills/read-frames/SKILL.md +2 -0
  74. package/claude/skills/record-screencast/SKILL.md +2 -0
  75. package/claude/skills/repo-metadata/SKILL.md +2 -0
  76. package/claude/skills/restate-plainly/SKILL.md +2 -0
  77. package/claude/skills/review-address/SKILL.md +3 -7
  78. package/claude/skills/review-address/references/post-review-findings.md +16 -0
  79. package/claude/skills/review-branch/SKILL.md +2 -0
  80. package/claude/skills/review-craft/SKILL.md +2 -0
  81. package/claude/skills/review-craft/references/ui.md +2 -2
  82. package/claude/skills/review-pr/SKILL.md +4 -8
  83. package/claude/skills/review-pr/references/body-variants.md +52 -0
  84. package/claude/skills/review-pr/references/key-changes.md +16 -0
  85. package/claude/skills/review-pr/references/post.md +15 -43
  86. package/claude/skills/review-ui/SKILL.md +3 -1
  87. package/claude/skills/review-ui/references/post.md +4 -2
  88. package/claude/skills/role-orchestrator/SKILL.md +5 -14
  89. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +8 -0
  90. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +13 -0
  91. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +1 -1
  92. package/claude/skills/role-orchestrator/references/orchestrator-inbound.md +23 -0
  93. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -33
  94. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +1 -21
  95. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +1 -1
  96. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +38 -0
  97. package/claude/skills/role-orchestrator/references/orchestrator-watch.md +26 -0
  98. package/claude/skills/role-planner/SKILL.md +2 -0
  99. package/claude/skills/role-reviewer/REQUIREMENT.md +1 -1
  100. package/claude/skills/role-reviewer/SKILL.md +2 -0
  101. package/claude/skills/role-worker/SKILL.md +2 -0
  102. package/claude/skills/search-craft/REQUIREMENT.md +1 -1
  103. package/claude/skills/search-craft/SKILL.md +5 -1
  104. package/claude/skills/search-craft/references/adopted.md +30 -0
  105. package/claude/skills/seed-sync/SKILL.md +2 -0
  106. package/claude/skills/session-compact/SKILL.md +2 -0
  107. package/claude/skills/session-map/SKILL.md +2 -0
  108. package/claude/skills/session-relay/SKILL.md +2 -0
  109. package/claude/skills/session-resume/SKILL.md +2 -0
  110. package/claude/skills/session-worktree/SKILL.md +3 -11
  111. package/claude/skills/session-worktree/references/install-command.md +20 -0
  112. package/claude/skills/sketch-design/SKILL.md +2 -0
  113. package/claude/skills/systematic-debugging/SKILL.md +2 -0
  114. package/claude/skills/target-check/SKILL.md +2 -0
  115. package/claude/skills/target-setup/SKILL.md +2 -0
  116. package/claude/skills/target-setup/references/detect.md +8 -0
  117. package/claude/skills/target-setup/references/indexes.md +13 -0
  118. package/claude/skills/target-setup/references/verify.md +8 -0
  119. package/claude/skills/task-board/SKILL.md +2 -0
  120. package/claude/skills/teach-workspace/SKILL.md +5 -15
  121. package/claude/skills/teach-workspace/references/lesson-build.md +75 -0
  122. package/claude/skills/teach-workspace/references/lesson-craft.md +13 -69
  123. package/claude/skills/teach-workspace/references/revisit.md +22 -0
  124. package/claude/skills/test-craft/SKILL.md +2 -0
  125. package/claude/skills/test-first/SKILL.md +4 -0
  126. package/claude/skills/test-first/references/adopted.md +36 -0
  127. package/claude/skills/ui-checklist/SKILL.md +2 -0
  128. package/claude/skills/ux-audit/SKILL.md +2 -0
  129. package/claude/skills/ux-measure/SKILL.md +2 -0
  130. package/claude/skills/ux-walkthrough/SKILL.md +2 -0
  131. package/claude/skills/video-craft/SKILL.md +2 -0
  132. package/claude/skills/write-human/SKILL.md +3 -1
  133. package/claude/skills/write-human/references/machine-tells.md +13 -0
  134. package/claude/skills/youtube-transcripts/SKILL.md +2 -0
  135. package/docs/agents/canvas.md +24 -6
  136. package/docs/agents/capture.md +6 -6
  137. package/docs/agents/commands.md +5 -5
  138. package/docs/agents/index.md +5 -5
  139. package/docs/agents/install-and-sync.md +7 -0
  140. package/docs/agents/key-changes.md +1 -1
  141. package/docs/agents/pr-evidence.md +38 -15
  142. package/docs/agents/pr-local.md +19 -13
  143. package/docs/agents/pr-preview.md +44 -21
  144. package/docs/agents/records-stale.md +22 -9
  145. package/docs/agents/records.md +1 -1
  146. package/docs/agents/sandbox.md +29 -5
  147. package/docs/agents/scripting.md +8 -1
  148. package/docs/agents/skills-audit.md +11 -3
  149. package/docs/agents/slides.md +15 -3
  150. package/docs/workflow/ai-workflow.md +1 -1
  151. package/docs/workflow/index.md +1 -0
  152. package/docs/workflow/operating-model.md +5 -6
  153. package/docs/workflow/parallel-features.md +23 -2
  154. package/docs/workflow/skill-map-on-request.md +42 -0
  155. package/docs/workflow/skill-map.md +3 -34
  156. package/docs/workflow/zshrc-aliases.md +1 -1
  157. package/package.json +1 -1
  158. package/scripts/core/regen-hero.ts +1 -1
  159. package/scripts/lib/sandbox-git.sh +1 -9
  160. package/scripts/lib/sandbox-path.sh +6 -162
  161. package/scripts/sandbox-hook.sh +99 -0
  162. package/src/audits/run.ts +1 -1
  163. package/src/canvas/address.ts +12 -3
  164. package/src/canvas/capture.ts +1 -9
  165. package/src/canvas/client/inspector/align-grid.tsx +162 -0
  166. package/src/canvas/client/inspector/color-field.tsx +398 -0
  167. package/src/canvas/client/inspector/color-inputs.tsx +139 -0
  168. package/src/canvas/client/inspector/color-picker.tsx +668 -0
  169. package/src/canvas/client/inspector/color-space.ts +240 -0
  170. package/src/canvas/client/inspector/field.tsx +5 -1
  171. package/src/canvas/client/inspector/fields.css +547 -17
  172. package/src/canvas/client/inspector/size-field.tsx +69 -0
  173. package/src/canvas/client/inspector/values.ts +166 -1
  174. package/src/canvas/client/inspector.tsx +236 -73
  175. package/src/canvas/client/selection.tsx +197 -0
  176. package/src/canvas/client/shell.css +104 -12
  177. package/src/canvas/client/state.ts +69 -9
  178. package/src/canvas/client/surface.tsx +147 -22
  179. package/src/canvas/content.ts +50 -1
  180. package/src/canvas/edit.ts +37 -4
  181. package/src/canvas/editing.ts +203 -0
  182. package/src/canvas/global.d.ts +15 -0
  183. package/src/canvas/server.ts +149 -12
  184. package/src/canvas/tokens.ts +59 -17
  185. package/src/capture/render.ts +45 -7
  186. package/src/capture/sources.ts +43 -0
  187. package/src/census/count.ts +2 -2
  188. package/src/claude/cases/workflow.ts +5 -0
  189. package/src/claude/seeds.ts +2 -2
  190. package/src/claude/skills-audit.ts +96 -14
  191. package/src/claude/skills-drift.ts +1 -1
  192. package/src/claude/skills-families.ts +36 -0
  193. package/src/claude/skills-list.ts +76 -15
  194. package/src/commands/audits.ts +2 -2
  195. package/src/commands/canvas.ts +179 -2
  196. package/src/commands/claude.ts +94 -11
  197. package/src/commands/design.ts +142 -2
  198. package/src/commands/feedback.ts +3 -3
  199. package/src/commands/gov.ts +1 -1
  200. package/src/commands/hooks.ts +1 -1
  201. package/src/commands/indexes.ts +1 -1
  202. package/src/commands/init.ts +1 -1
  203. package/src/commands/intake.ts +1 -1
  204. package/src/commands/migrate.ts +1 -1
  205. package/src/commands/pr.ts +105 -58
  206. package/src/commands/records.ts +89 -7
  207. package/src/commands/repo.ts +1 -1
  208. package/src/commands/sandbox.ts +103 -11
  209. package/src/commands/sessions.ts +1 -1
  210. package/src/commands/slides.ts +31 -77
  211. package/src/commands/sync.ts +1 -1
  212. package/src/commands/tasks.ts +2 -2
  213. package/src/commands/teach.ts +1 -1
  214. package/src/commands/transcripts.ts +1 -1
  215. package/src/commands/worktrees.ts +1 -1
  216. package/src/comments/scan.ts +1 -1
  217. package/src/comments/trend.ts +1 -1
  218. package/src/context/audit.ts +1 -1
  219. package/src/context/citations.ts +1 -1
  220. package/src/context/classify/extract.ts +2 -2
  221. package/src/design/project-fonts.ts +392 -0
  222. package/src/design/tokens.ts +2 -18
  223. package/src/docs/read.ts +1 -1
  224. package/src/{github.ts → feedback/github.ts} +2 -2
  225. package/src/{client-commands.ts → gate/client-commands.ts} +1 -1
  226. package/src/gate/measures.ts +53 -13
  227. package/src/gate/sequencer.ts +1 -1
  228. package/src/gate/stages.ts +9 -0
  229. package/src/{git-files.ts → git/files.ts} +2 -2
  230. package/src/{git-ignore.ts → git/ignore.ts} +1 -1
  231. package/src/{worktree.ts → git/worktree.ts} +1 -1
  232. package/src/gov/citations.ts +2 -2
  233. package/src/gov/install.ts +1 -1
  234. package/src/gov/payload.ts +1 -1
  235. package/src/gov/superseded.ts +3 -3
  236. package/src/gov/test-order.ts +2 -2
  237. package/src/help.ts +1 -1
  238. package/src/hooks/post-merge.ts +1 -1
  239. package/src/indexes/walk.ts +1 -1
  240. package/src/labels/audit.ts +1 -1
  241. package/src/markdown/ceiling.ts +1 -1
  242. package/src/markdown/files.ts +1 -1
  243. package/src/migrate/apply.ts +1 -1
  244. package/src/pr/bijection.ts +1 -1
  245. package/src/pr/evidence.ts +96 -43
  246. package/src/pr/local.ts +16 -10
  247. package/src/pr/preview.ts +78 -2
  248. package/src/record-root.ts +1 -0
  249. package/src/records/backup.ts +2 -1
  250. package/src/records/push-guard.ts +1 -1
  251. package/src/records/stale-canonical.ts +146 -0
  252. package/src/records/stale.ts +1 -1
  253. package/src/sandbox/headless/dispatch.ts +264 -0
  254. package/src/sandbox/headless/run.ts +653 -0
  255. package/src/sandbox/headless/watch.ts +145 -0
  256. package/src/sandbox/hooks.ts +128 -0
  257. package/src/sandbox/provision.ts +607 -0
  258. package/src/sandbox/tree.ts +99 -4
  259. package/src/secrets/marker.ts +2 -2
  260. package/src/secrets/scan.ts +2 -2
  261. package/src/sessions/claim.ts +1 -1
  262. package/src/sessions/resolve.ts +1 -1
  263. package/src/sessions/transfer/bundle.ts +1 -1
  264. package/src/shipped/references.ts +2 -2
  265. package/src/slides/convert/export.ts +2 -16
  266. package/src/slides/decks.ts +110 -0
  267. package/src/slides/package/fonts.ts +6 -2
  268. package/src/standards/audit.ts +2 -2
  269. package/src/standards/read.ts +1 -1
  270. package/src/sync/check.ts +2 -2
  271. package/src/sync/engine.ts +2 -2
  272. package/src/sync/git.ts +1 -1
  273. package/src/sync/history.ts +1 -1
  274. package/src/sync/reverse.ts +1 -1
  275. package/src/sync/seeds-report.ts +1 -1
  276. package/src/sync/target.ts +1 -1
  277. package/src/sync/workflow.ts +1 -1
  278. package/src/targets/pulls.ts +2 -2
  279. package/src/targets/sweep.ts +2 -2
  280. package/src/tasks/answers.ts +1 -1
  281. package/src/tasks/archive.ts +1 -1
  282. package/src/tasks/reach.ts +1 -1
  283. package/src/tasks/trunk.ts +1 -1
  284. package/src/teach/nav.ts +1 -1
  285. package/src/tooling/inject.ts +2 -2
  286. package/src/tooling/subfolder.ts +1 -1
  287. package/src/web/readme-citations.ts +2 -2
  288. package/src/worktrees/bare-flag.ts +1 -1
  289. package/src/worktrees/reclaim.ts +2 -2
  290. package/src/worktrees/remove.ts +1 -1
  291. package/standards/architecture.md +1 -0
  292. package/standards/canvas.md +3 -2
  293. package/standards/requirements.md +2 -0
  294. package/standards/skill-practice.md +2 -1
  295. package/standards/skill.md +2 -0
  296. package/standards/wiki.md +12 -3
  297. package/tooling/base/configs/.github/workflows/pr-local-preview.yml +2 -2
  298. package/scripts/lib/sandbox-dispatch.sh +0 -190
  299. package/scripts/manage-sandbox.sh +0 -472
  300. package/src/slides/layouts.ts +0 -478
  301. package/src/slides/parse.ts +0 -84
  302. package/src/slides/render.ts +0 -110
  303. package/src/slides/styles.ts +0 -96
  304. /package/claude/skills/write-human/references/{source-material.md → adopted.md} +0 -0
  305. /package/src/{commands/feedback-format.ts → feedback/body.ts} +0 -0
  306. /package/src/{github-format.ts → feedback/github-format.ts} +0 -0
  307. /package/src/{git-base.ts → git/base.ts} +0 -0
  308. /package/src/{git-env.ts → git/env.ts} +0 -0
  309. /package/src/{frontmatter.ts → markdown/frontmatter.ts} +0 -0
  310. /package/src/{binary.ts → sweep/binary.ts} +0 -0
  311. /package/src/{exempt-marker.ts → sweep/exempt-marker.ts} +0 -0
  312. /package/src/{copy.ts → sync/copy.ts} +0 -0
  313. /package/src/{target.ts → targets/validate.ts} +0 -0
  314. /package/src/{paths.ts → tasks/paths.ts} +0 -0
  315. /package/src/{seed-marker.ts → tooling/seed-marker.ts} +0 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Conversions between the hex the writer takes and the spaces the picker
3
+ * edits in. Channels are 0 to 255, hues are degrees, and every other component
4
+ * is 0 to 100. LCH is CIE LCH as CSS `lch()` defines it, and the matrices are
5
+ * the ones CSS Color 4 publishes in its sample code.
6
+ */
7
+
8
+ export interface Rgb {
9
+ readonly r: number
10
+ readonly g: number
11
+ readonly b: number
12
+ }
13
+
14
+ export interface Hsv {
15
+ readonly h: number
16
+ readonly s: number
17
+ readonly v: number
18
+ }
19
+
20
+ export interface Hsl {
21
+ readonly h: number
22
+ readonly s: number
23
+ readonly l: number
24
+ }
25
+
26
+ export interface Lch {
27
+ readonly l: number
28
+ readonly c: number
29
+ readonly h: number
30
+ }
31
+
32
+ type Vector = readonly [number, number, number]
33
+ type Matrix = readonly [Vector, Vector, Vector]
34
+
35
+ const LINEAR_SRGB_TO_XYZ: Matrix = [
36
+ [506752 / 1228815, 87881 / 245763, 12673 / 70218],
37
+ [87098 / 409605, 175762 / 245763, 12673 / 175545],
38
+ [7918 / 409605, 87881 / 737289, 1001167 / 1053270],
39
+ ]
40
+
41
+ const XYZ_TO_LINEAR_SRGB: Matrix = [
42
+ [12831 / 3959, -329 / 214, -1974 / 3959],
43
+ [-851781 / 878810, 1648619 / 878810, 36519 / 878810],
44
+ [705 / 12673, -2585 / 12673, 705 / 667],
45
+ ]
46
+
47
+ /** Bradford chromatic adaptation, since sRGB is D65 and `lch()` is D50. */
48
+ const D65_TO_D50: Matrix = [
49
+ [1.0479297925449969, 0.022946870601609652, -0.05019226628920524],
50
+ [0.02962780877005599, 0.9904344267538799, -0.017073799063418826],
51
+ [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371],
52
+ ]
53
+
54
+ const D50_TO_D65: Matrix = [
55
+ [0.955473421488075, -0.02309845494876471, 0.06325924320057072],
56
+ [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
57
+ [0.012314014864481998, -0.020507649298898964, 1.330365926242124],
58
+ ]
59
+
60
+ const D50_WHITE: Vector = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585]
61
+
62
+ const EPSILON = 216 / 24389
63
+ const KAPPA = 24389 / 27
64
+
65
+ /** Chroma below this reads as no hue, which rounding noise never reaches. */
66
+ const ACHROMATIC = 0.02
67
+
68
+ function multiply(matrix: Matrix, vector: Vector): Vector {
69
+ const [row0, row1, row2] = matrix.map(
70
+ (row) => row[0] * vector[0] + row[1] * vector[1] + row[2] * vector[2],
71
+ )
72
+ return [row0 ?? 0, row1 ?? 0, row2 ?? 0]
73
+ }
74
+
75
+ function clamp(value: number, low: number, high: number): number {
76
+ return Math.min(high, Math.max(low, value))
77
+ }
78
+
79
+ function wrapHue(hue: number): number {
80
+ return ((hue % 360) + 360) % 360
81
+ }
82
+
83
+ export function hexToRgb(hex: string): Rgb {
84
+ return {
85
+ r: parseInt(hex.slice(0, 2), 16),
86
+ g: parseInt(hex.slice(2, 4), 16),
87
+ b: parseInt(hex.slice(4, 6), 16),
88
+ }
89
+ }
90
+
91
+ export function rgbToHex({ r, g, b }: Rgb): string {
92
+ return [r, g, b]
93
+ .map((part) =>
94
+ Math.round(clamp(part, 0, 255))
95
+ .toString(16)
96
+ .padStart(2, '0'),
97
+ )
98
+ .join('')
99
+ }
100
+
101
+ /**
102
+ * HSV off RGB. A color with no saturation has no hue of its own, so it takes
103
+ * the one handed in, which keeps a grey's hue slider where the operator left it.
104
+ */
105
+ export function rgbToHsv({ r, g, b }: Rgb, hue = 0): Hsv {
106
+ const max = Math.max(r, g, b)
107
+ const delta = max - Math.min(r, g, b)
108
+ return {
109
+ h: delta === 0 ? hue : hueOf(r, g, b, max, delta),
110
+ s: max === 0 ? 0 : (delta / max) * 100,
111
+ v: (max / 255) * 100,
112
+ }
113
+ }
114
+
115
+ export function hsvToRgb({ h, s, v }: Hsv): Rgb {
116
+ const value = (v / 100) * 255
117
+ const chroma = value * (s / 100)
118
+ return fromChroma(h, chroma, value - chroma)
119
+ }
120
+
121
+ /** HSL off RGB, taking the hue handed in when there is no saturation. */
122
+ export function rgbToHsl({ r, g, b }: Rgb, hue = 0): Hsl {
123
+ const max = Math.max(r, g, b)
124
+ const min = Math.min(r, g, b)
125
+ const delta = max - min
126
+ const lightness = (max + min) / 2 / 255
127
+ const saturation =
128
+ delta === 0 ? 0 : delta / 255 / (1 - Math.abs(2 * lightness - 1))
129
+ return {
130
+ h: delta === 0 ? hue : hueOf(r, g, b, max, delta),
131
+ s: saturation * 100,
132
+ l: lightness * 100,
133
+ }
134
+ }
135
+
136
+ export function hslToRgb({ h, s, l }: Hsl): Rgb {
137
+ const lightness = l / 100
138
+ const chroma = (1 - Math.abs(2 * lightness - 1)) * (s / 100) * 255
139
+ return fromChroma(h, chroma, lightness * 255 - chroma / 2)
140
+ }
141
+
142
+ function hueOf(
143
+ r: number,
144
+ g: number,
145
+ b: number,
146
+ max: number,
147
+ delta: number,
148
+ ): number {
149
+ const sector =
150
+ max === r
151
+ ? (g - b) / delta + (g < b ? 6 : 0)
152
+ : max === g
153
+ ? (b - r) / delta + 2
154
+ : (r - g) / delta + 4
155
+ return sector * 60
156
+ }
157
+
158
+ function fromChroma(hue: number, chroma: number, base: number): Rgb {
159
+ const sector = wrapHue(hue) / 60
160
+ const second = chroma * (1 - Math.abs((sector % 2) - 1))
161
+ const [r, g, b]: Vector =
162
+ sector < 1
163
+ ? [chroma, second, 0]
164
+ : sector < 2
165
+ ? [second, chroma, 0]
166
+ : sector < 3
167
+ ? [0, chroma, second]
168
+ : sector < 4
169
+ ? [0, second, chroma]
170
+ : sector < 5
171
+ ? [second, 0, chroma]
172
+ : [chroma, 0, second]
173
+ return { r: r + base, g: g + base, b: b + base }
174
+ }
175
+
176
+ function toLinear(channel: number): number {
177
+ const value = channel / 255
178
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4
179
+ }
180
+
181
+ function fromLinear(value: number): number {
182
+ const encoded =
183
+ value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055
184
+ return encoded * 255
185
+ }
186
+
187
+ /** CIE LCH off RGB, taking the hue handed in when there is no chroma. */
188
+ export function rgbToLch({ r, g, b }: Rgb, hue = 0): Lch {
189
+ const xyz = multiply(
190
+ D65_TO_D50,
191
+ multiply(LINEAR_SRGB_TO_XYZ, [toLinear(r), toLinear(g), toLinear(b)]),
192
+ )
193
+ const f = (ratio: number): number =>
194
+ ratio > EPSILON ? Math.cbrt(ratio) : (KAPPA * ratio + 16) / 116
195
+ const fx = f(xyz[0] / D50_WHITE[0])
196
+ const fy = f(xyz[1] / D50_WHITE[1])
197
+ const fz = f(xyz[2] / D50_WHITE[2])
198
+ const a = 500 * (fx - fy)
199
+ const bAxis = 200 * (fy - fz)
200
+ const chroma = Math.sqrt(a * a + bAxis * bAxis)
201
+ return {
202
+ l: 116 * fy - 16,
203
+ c: chroma < ACHROMATIC ? 0 : chroma,
204
+ h:
205
+ chroma < ACHROMATIC
206
+ ? hue
207
+ : wrapHue((Math.atan2(bAxis, a) * 180) / Math.PI),
208
+ }
209
+ }
210
+
211
+ /**
212
+ * RGB off CIE LCH. A color outside sRGB has no faithful hex, so each channel
213
+ * clamps and `isClamped` says so, letting the row show the color written.
214
+ */
215
+ export function lchToRgb({ l, c, h }: Lch): {
216
+ readonly rgb: Rgb
217
+ readonly isClamped: boolean
218
+ } {
219
+ const radians = (h * Math.PI) / 180
220
+ const fy = (l + 16) / 116
221
+ const fx = fy + (c * Math.cos(radians)) / 500
222
+ const fz = fy - (c * Math.sin(radians)) / 200
223
+ const ratios: Vector = [
224
+ fx ** 3 > EPSILON ? fx ** 3 : (116 * fx - 16) / KAPPA,
225
+ l > KAPPA * EPSILON ? fy ** 3 : l / KAPPA,
226
+ fz ** 3 > EPSILON ? fz ** 3 : (116 * fz - 16) / KAPPA,
227
+ ]
228
+ const xyz: Vector = [
229
+ ratios[0] * D50_WHITE[0],
230
+ ratios[1] * D50_WHITE[1],
231
+ ratios[2] * D50_WHITE[2],
232
+ ]
233
+ const channels = multiply(XYZ_TO_LINEAR_SRGB, multiply(D50_TO_D65, xyz)).map(
234
+ fromLinear,
235
+ )
236
+ /* Half a step either side rounds into range, so it is not a clamp. */
237
+ const isClamped = channels.some((part) => part < -0.5 || part > 255.5)
238
+ const [r, g, b] = channels.map((part) => clamp(part, 0, 255))
239
+ return { rgb: { r: r ?? 0, g: g ?? 0, b: b ?? 0 }, isClamped }
240
+ }
@@ -1,5 +1,5 @@
1
1
  /** @jsxImportSource preact */
2
- import type { JSX } from 'preact'
2
+ import type { ComponentChildren, JSX } from 'preact'
3
3
  import { useRef } from 'preact/hooks'
4
4
  import { scrubStep } from '@/canvas/client/inspector/values'
5
5
 
@@ -35,6 +35,8 @@ interface FieldProps {
35
35
  readonly onCommit: (value: string) => void
36
36
  /** Makes the glyph a drag handle. Typing stays the keyboard path. */
37
37
  readonly scrub?: Scrub
38
+ /** A control inside the field's box after the value, such as a mode menu. */
39
+ readonly trailing?: ComponentChildren
38
40
  }
39
41
 
40
42
  const NUMBER = /^-?\d*\.?\d+$/
@@ -69,6 +71,7 @@ export function Field({
69
71
  placeholder,
70
72
  onCommit,
71
73
  scrub,
74
+ trailing,
72
75
  }: FieldProps): JSX.Element {
73
76
  const input = useRef<HTMLInputElement>(null)
74
77
  const handle = useRef<HTMLSpanElement>(null)
@@ -169,6 +172,7 @@ export function Field({
169
172
  if (event.key === 'Escape') event.currentTarget.value = initial
170
173
  }}
171
174
  />
175
+ {trailing}
172
176
  </div>
173
177
  )
174
178
  }