@hajimism/dek 0.3.3 → 0.5.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 (105) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.ja.md +2 -2
  3. package/README.md +2 -2
  4. package/package.json +5 -3
  5. package/skills/dek/SKILL.md +10 -3
  6. package/skills/dek-design/SKILL.md +42 -0
  7. package/src/cli/address.ts +95 -18
  8. package/src/cli/build.ts +2 -2
  9. package/src/cli/check.ts +2 -1
  10. package/src/cli/commands.ts +57 -19
  11. package/src/cli/contract.ts +58 -4
  12. package/src/cli/files.ts +2 -5
  13. package/src/cli/flags.ts +1 -1
  14. package/src/cli/format.ts +5 -4
  15. package/src/cli/goto.ts +44 -11
  16. package/src/cli/init.ts +10 -5
  17. package/src/cli/lint.ts +36 -16
  18. package/src/cli/ls.ts +5 -1
  19. package/src/cli/main.ts +51 -14
  20. package/src/cli/new.ts +28 -8
  21. package/src/cli/pptx.ts +9 -4
  22. package/src/cli/ref.ts +92 -9
  23. package/src/cli/result.ts +47 -6
  24. package/src/cli/scope.ts +57 -29
  25. package/src/cli/shot.ts +78 -6
  26. package/src/cli/show.ts +15 -2
  27. package/src/cli/text.ts +88 -11
  28. package/src/cli/theme.ts +58 -10
  29. package/src/cli/tty.ts +16 -2
  30. package/src/cli/voice.ts +15 -9
  31. package/src/core/alignment.ts +320 -0
  32. package/src/core/annotation-rows.ts +16 -4
  33. package/src/core/annotations.ts +19 -1
  34. package/src/core/assets.ts +18 -10
  35. package/src/core/attribute-value.ts +188 -0
  36. package/src/core/build.ts +3 -3
  37. package/src/core/config.ts +61 -12
  38. package/src/core/css-scan.ts +173 -39
  39. package/src/core/css-transform.ts +57 -11
  40. package/src/core/css.ts +19 -13
  41. package/src/core/diagnostic.ts +5 -1
  42. package/src/core/escape.ts +43 -0
  43. package/src/core/foreign-text.ts +138 -0
  44. package/src/core/github.ts +1 -0
  45. package/src/core/html-entities.ts +318 -0
  46. package/src/core/html-scan.ts +49 -27
  47. package/src/core/html.ts +21 -15
  48. package/src/core/line-break.ts +275 -0
  49. package/src/core/lint/asset-refs.ts +50 -8
  50. package/src/core/lint/files.ts +30 -5
  51. package/src/core/lint/keys.ts +7 -3
  52. package/src/core/lint/slide-html.ts +24 -19
  53. package/src/core/lint/slide-script.ts +7 -5
  54. package/src/core/mv.ts +57 -27
  55. package/src/core/order.ts +14 -0
  56. package/src/core/pack.ts +2 -2
  57. package/src/core/path.ts +8 -2
  58. package/src/core/playwright-module.d.ts +13 -1
  59. package/src/core/playwright-motion.ts +1 -0
  60. package/src/core/playwright-sheet.ts +1 -0
  61. package/src/core/playwright-visual.ts +207 -30
  62. package/src/core/playwright.ts +279 -44
  63. package/src/core/pptx-package.ts +461 -33
  64. package/src/core/pptx-paint.ts +1168 -0
  65. package/src/core/pptx-plan.ts +879 -0
  66. package/src/core/pptx-text.ts +204 -47
  67. package/src/core/pptx-theme.ts +222 -0
  68. package/src/core/pptx.ts +56 -8
  69. package/src/core/prose.ts +23 -0
  70. package/src/core/pseudo-text.ts +32 -10
  71. package/src/core/ref-name.ts +52 -8
  72. package/src/core/ref.ts +308 -54
  73. package/src/core/resolve.ts +23 -11
  74. package/src/core/rumdl.ts +51 -25
  75. package/src/core/safe-fs.ts +19 -2
  76. package/src/core/sandbox.ts +217 -0
  77. package/src/core/shot/morph.ts +1 -1
  78. package/src/core/shot/motion.ts +2 -3
  79. package/src/core/shot/still.ts +2 -2
  80. package/src/core/shot-cache.ts +26 -17
  81. package/src/core/slide-eval-worker.ts +1 -0
  82. package/src/core/slide-measure.ts +51 -1
  83. package/src/core/slide-script-eval.ts +12 -8
  84. package/src/core/slide-script.ts +114 -20
  85. package/src/core/spawn.ts +131 -32
  86. package/src/core/sync.ts +5 -14
  87. package/src/core/tar.ts +315 -0
  88. package/src/core/text-contrast.ts +2 -0
  89. package/src/core/toml-keys.ts +116 -51
  90. package/src/core/url-attributes.ts +27 -9
  91. package/src/core/visual-cache.ts +2 -0
  92. package/src/core/visual.ts +182 -24
  93. package/src/core/voice.ts +5 -6
  94. package/src/core/zod.ts +9 -1
  95. package/src/server/auth.ts +21 -0
  96. package/src/server/dev.ts +26 -7
  97. package/src/server/lock.ts +50 -7
  98. package/src/server/routes.ts +7 -2
  99. package/src/server/serial.ts +8 -2
  100. package/src/theme/default.css +4 -0
  101. package/src/video/bake.ts +6 -2
  102. package/src/video/mux.ts +27 -3
  103. package/src/video/recorder.ts +3 -2
  104. package/src/video/worker.ts +1 -0
  105. package/src/voice/engine.ts +81 -18
package/CHANGELOG.md CHANGED
@@ -2,6 +2,142 @@
2
2
 
3
3
  Release notes for `@hajimism/dek`. From 0.1.0 on, release-please writes this file from the Conventional Commits on `main`.
4
4
 
5
+ ## [0.5.0](https://github.com/hajimism/dek/compare/v0.4.0...v0.5.0) (2026-10-27)
6
+
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * **ref:** a dek.toml that pins a ref whose name holds a control character or a character a person cannot see no longer loads until that line is removed; `dekc pack` refuses a deck so named until its folder is renamed; and `dekc new` and `dekc init --deck` refuse such a name, which they took before. Every other name works as before.
11
+ * where Chromium's sandbox does not start, `dekc shot <ref>` fails until DEK_ALLOW_UNSANDBOXED=1 is set.
12
+
13
+ ### Features
14
+
15
+ * **ref:** report the characters in a ref that a person cannot see ([5ca233e](https://github.com/hajimism/dek/commit/5ca233e186125e821f1341cc790636f26654274a))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **build:** keep a minified stylesheet reading as its source where an escape meets a newline or a comment ([41fd1ba](https://github.com/hajimism/dek/commit/41fd1ba08abc63c8af1d9deb8e2b9d70bb5ceab5))
21
+ * **build:** minify a stylesheet into one that reads as its source does ([bdcffb9](https://github.com/hajimism/dek/commit/bdcffb9edaf7238b33bdf740dc4072c6f8cdaaa1))
22
+ * **cli:** at the project root, hint a command only for the decks that have what it names ([fe3f8fb](https://github.com/hajimism/dek/commit/fe3f8fb12c178a7d8bfcb40b4ed16b52d03a08ae))
23
+ * **cli:** hint `dekc ls` for a deck that is not there, not `dekc ls <it>` ([1ee4554](https://github.com/hajimism/dek/commit/1ee455439734f57eaf03c6c91fd78a9423f4812f))
24
+ * **cli:** name a ref by the ref in the hints about it ([e9d1a47](https://github.com/hajimism/dek/commit/e9d1a47fe282b766bed189212891f1aafcc38a94))
25
+ * **dev:** print a failure of the dev server's lint or voice run instead of dropping it ([14a8e8c](https://github.com/hajimism/dek/commit/14a8e8ceb75247d850407aeeef3bba56f5525a88))
26
+ * give up on a voice engine, a dev server, or a rumdl that never answers ([19023f2](https://github.com/hajimism/dek/commit/19023f2c3987c4e60f2fe7440d4e1d5369ec2f10))
27
+ * keep a file's mode when dek writes it anew ([085d7e8](https://github.com/hajimism/dek/commit/085d7e8e4be2895c975cd76668d4c7af91937d96))
28
+ * **lint:** list rumdl as skipped when what it printed is not SARIF ([74582e0](https://github.com/hajimism/dek/commit/74582e07153aeb77211083e87309cbe6214e8ba3))
29
+ * **lint:** name the path beside a stray space in DEK021's hint only when it is a plain word ([b030691](https://github.com/hajimism/dek/commit/b03069178ab6b318749c0296aca5fc916ebc2cec))
30
+ * **lint:** read a URL in DEK011 as the browser's URL parser reads it ([c0950a5](https://github.com/hajimism/dek/commit/c0950a59b6c25f1c561c101a07750b89c0a87283))
31
+ * **lint:** say to run lint --visual again after lint --visual fails ([a00da44](https://github.com/hajimism/dek/commit/a00da4437aadf6373a4e888213f26d6efdd7462a))
32
+ * **ls:** keep a ref's title, event, and date to one line, as AGENTS.md does ([af85b6e](https://github.com/hajimism/dek/commit/af85b6e8ef63175cfb3166465e533e6b361f166e))
33
+ * **mv:** write script.md, slides, and voice.toml whole, never over the old file ([a7a22b3](https://github.com/hajimism/dek/commit/a7a22b34a1fb073181966bec4a7c7e88b88757ce))
34
+ * print no color code a deck or a ref wrote, only dek's own ([27b6751](https://github.com/hajimism/dek/commit/27b6751eb48d84e2538983c618889f74389d5caa))
35
+ * read a pack's or a GitHub tarball's tar with dek's own reader, so it unpacks no more than was counted ([bdda596](https://github.com/hajimism/dek/commit/bdda596bf5974e6c30f5d273090327d0ed114a6b))
36
+ * read each attribute value as the browser does, its character references decoded ([65a58d1](https://github.com/hajimism/dek/commit/65a58d11497648690c539c8caa4bbe49c246a4c8))
37
+ * read the keys after an array or inline table written over several lines ([fa8f268](https://github.com/hajimism/dek/commit/fa8f2680bec36c3b092f5b7eef5749cd4146d64a))
38
+ * read url() and a string cut short by CR or FF where the browser does ([55296f2](https://github.com/hajimism/dek/commit/55296f2a8f04ffdea958b835132bc2ad4c8054d5))
39
+ * **ref:** keep a ref's names out of hints, and to one short line in messages ([67f7d19](https://github.com/hajimism/dek/commit/67f7d19268cb1032978d62a1a0fe1ec2a209197c))
40
+ * **ref:** refuse a pack whose deck's name holds a control character or one a person cannot see ([373f14f](https://github.com/hajimism/dek/commit/373f14f368057677c33d9c8bbaccc427e048d790))
41
+ * **sample:** set the note on why-dek's read-only card in ink ([586d09d](https://github.com/hajimism/dek/commit/586d09d7e706ae56b731ee9c5a4b6f1b03406d2e))
42
+ * say whenever Chromium draws without its sandbox, and draw a ref there only when asked ([293b3ff](https://github.com/hajimism/dek/commit/293b3ffd0fd12ff042831fc4816e96025d7336af))
43
+ * **server:** keep what the dev server serves out of other sites' pages and frames ([260b0f0](https://github.com/hajimism/dek/commit/260b0f081de5d9821312456abbec2849dfac7fd7))
44
+ * **shot:** send a ref's broken slide script to its author, not to lint ([c1045fa](https://github.com/hajimism/dek/commit/c1045faf8583ca61281b825811a172a72ec0bb6c))
45
+ * **show:** keep the name of keyframes written as a string in the theme excerpt ([072b078](https://github.com/hajimism/dek/commit/072b07848a2799f7d2e98f42962330c2e407bbee))
46
+ * **show:** say a slide is a ref's, and start a part on a line no file can write ([b6b2389](https://github.com/hajimism/dek/commit/b6b2389fe5cf8dfc9dac4fefa1eab88737d7075a))
47
+ * sort dict.toml, decks, and slide files the same way on every machine ([29a3f3c](https://github.com/hajimism/dek/commit/29a3f3c4c6073fbdc5808c9921d296fa8e4125cf))
48
+ * start every program dek runs with the environment as it is now ([ee6b319](https://github.com/hajimism/dek/commit/ee6b319d2772ff39ea82cf3dd8fd763d4fb130c8))
49
+ * stop a command at its timeout, even one that ignores SIGTERM or leaves a process behind ([3b05db7](https://github.com/hajimism/dek/commit/3b05db7ee9c17e68e5d580d744dc457856c29678))
50
+ * **tar:** leave AppleDouble names out, and number that record 27 ([c4fb0ef](https://github.com/hajimism/dek/commit/c4fb0efbb9cd0a73f5335f078fb9b08bca1d35ef))
51
+ * **video:** give up on an ffmpeg that never answers -version ([c0d3c6a](https://github.com/hajimism/dek/commit/c0d3c6a22d0dd8328782661ebc22384313d33487))
52
+ * **visual:** stop sending a DEK031 to theme.css when the theme alone was never measured ([b3321b3](https://github.com/hajimism/dek/commit/b3321b3302389743dd5f0606e2a6367e2635f14d))
53
+ * **voice:** stop waiting on a player that never ends after a say clip ([fc1082b](https://github.com/hajimism/dek/commit/fc1082bf3e8eb012bda822256fe51a067eb086b8))
54
+
55
+
56
+ ### Refactoring
57
+
58
+ * write the link preview image and remove the dev server's lock through safe-fs ([3ae94c8](https://github.com/hajimism/dek/commit/3ae94c8db6ae9bef4243bec3cc334e1a4a2b9e11))
59
+
60
+
61
+ ### Documentation
62
+
63
+ * **adr:** keep a ref's words out of dek's hints, and mark what a ref wrote wherever dek prints it ([e4f0787](https://github.com/hajimism/dek/commit/e4f07878aee0f0f887484b800778324a55e0f97d))
64
+ * **adr:** number the --json contract record 31 ([8108f37](https://github.com/hajimism/dek/commit/8108f3770b3c2b24efb57b2ed1064949a998e95b))
65
+ * **adr:** number the fast-check record 26, where this stack adds it ([389948e](https://github.com/hajimism/dek/commit/389948e6e8ef79842d8b429fac5e2d8e5d2ddeb2))
66
+ * **adr:** number the ref-words record 30, and point it at the trust record ([6925755](https://github.com/hajimism/dek/commit/692575572ab5a235ac1256c7b7b11e31a4ddc01f))
67
+ * **adr:** number the sandbox record 29, and point it at the trust record ([e082487](https://github.com/hajimism/dek/commit/e08248711777c3acabd8ac46c1c8a080302de961))
68
+ * **adr:** number the trust record 28, after the records already on this branch ([7a7fbd8](https://github.com/hajimism/dek/commit/7a7fbd8b9cb6f18af2d27f26a1fb962c3e85f30a))
69
+ * **adr:** read a pack's or a repository's tar with dek's own reader ([ba896c8](https://github.com/hajimism/dek/commit/ba896c84daec4a39fdc5d7fdd34c1cf5ccb52394))
70
+ * **adr:** read every attribute value as the browser does ([6a1c698](https://github.com/hajimism/dek/commit/6a1c698fbc74da129c034f175063d8c99a83f9bc))
71
+ * **adr:** say what dek does where Chromium's sandbox cannot start ([f2f940a](https://github.com/hajimism/dek/commit/f2f940ad4c0a216f9f0ec0085da72cfd15414282))
72
+ * **adr:** say whom dek trusts, and what each of them may make it do ([8f26099](https://github.com/hajimism/dek/commit/8f260990674b3c3105b40b276f9e90c6e013e1db))
73
+ * pass the suite timeout on every bun test line in CONTRIBUTING ([6c7182c](https://github.com/hajimism/dek/commit/6c7182cab0564f91088be06b5209c32b6f191efa))
74
+ * say how dek keeps a ref's words apart from its own ([756ba7c](https://github.com/hajimism/dek/commit/756ba7ca74ff05cbd109ef0eab270dea92c52b78))
75
+ * say how long dek waits on rumdl, a voice engine, and the dev server, and how large a ref may be ([e196cd8](https://github.com/hajimism/dek/commit/e196cd81ad83cd90286a91977258bb7c0882eaee))
76
+ * say lint reads an attribute's value and URL as the browser does ([4c67184](https://github.com/hajimism/dek/commit/4c671849309e7ff8015341862b9fc8ec860d0cb7))
77
+
78
+ ## [0.4.0](https://github.com/hajimism/dek/compare/v0.3.4...v0.4.0) (2026-10-27)
79
+
80
+
81
+ ### ⚠ BREAKING CHANGES
82
+
83
+ * **theme:** `dekc theme --json` lists in `layouts` only layouts whose names are plain words: letters, marks, and digits of any script and `@%+=:,./_-`, not starting with `=`. A layout whose name holds anything else, such as a space, a quote, `$`, `;`, or a backtick, is no longer in `layouts`; `unprintedLayouts`, present only when there are some, says how many were left out. `dekc theme <layout>` still prints such a layout's example.
84
+ * **ref:** a dek.toml whose [refs] pins a deck name with any other character, starting with a dash, not in NFC, or longer than 64 characters or 200 bytes, such as `owner/repo/talk(1)` or `file/a+b`, no longer loads. Delete the line the error points at, and for a pack its packs/<deck>.tgz and refs/file/<deck>, then pin the deck again under a name a ref can carry.
85
+
86
+ ### Bug Fixes
87
+
88
+ * **annotations:** show a deck, slide, or beat that is no plain word as a placeholder in the notes ([3b04e89](https://github.com/hajimism/dek/commit/3b04e89dc22f9ea421919c920594f53a9c02d3be))
89
+ * **cli:** say once where to find a deck's name a retyped command leaves as &lt;deck&gt; ([d4a57d9](https://github.com/hajimism/dek/commit/d4a57d9f7836b2ba2433dc634a136bb7e97be509))
90
+ * **config:** show a key that is no plain word as &lt;key&gt; in an error ([22476a7](https://github.com/hajimism/dek/commit/22476a712ecf0ee4e3c552b1e0fe6f35ac10cacf))
91
+ * **dev:** read .dek/server.json only as the dev server writes it ([c402cb6](https://github.com/hajimism/dek/commit/c402cb6282783425611274fbc0337cd6652add7e))
92
+ * **dev:** read the lock a dev server on port 80 writes ([665e0e2](https://github.com/hajimism/dek/commit/665e0e237c3a639a45fa36dc14ed8bd2e7ad9168))
93
+ * keep reads, writes, and suggested commands inside what the project and dek wrote ([23a667d](https://github.com/hajimism/dek/commit/23a667d199ee5f0b34040c4c33f0c0bcfd47f819))
94
+ * **lint:** name a remote file in DEK020's hint only when it is a plain word ([fb77d02](https://github.com/hajimism/dek/commit/fb77d028db1bb38bea6b43695838d52776b192a7))
95
+ * **lint:** name a slide or asset file in a hint only when it is a plain word ([eeb240c](https://github.com/hajimism/dek/commit/eeb240c418b615067bd66ff45ac6b70361d3bd1b))
96
+ * **lint:** show an unknown key that is no plain word as &lt;key&gt; in DEK008 ([f950def](https://github.com/hajimism/dek/commit/f950def7f80f06da46bd8c913ae84c8c849d5ddf))
97
+ * never follow a link under refs/ when writing a snapshot ([0c843ea](https://github.com/hajimism/dek/commit/0c843ea4193270dd1cc0fde4a04768e210efd911))
98
+ * never write or remove a pack's copy through a link under packs/ ([95980a6](https://github.com/hajimism/dek/commit/95980a631e90ee97eda24784ded50b042111512d))
99
+ * quote every name in a command dek suggests ([d1e608d](https://github.com/hajimism/dek/commit/d1e608d5676e46b7947bd5f43a3315269fd27773))
100
+ * read a project's files only through links that stay inside it ([7ae3815](https://github.com/hajimism/dek/commit/7ae38152eadd6f58d67034169ade9dd749038002))
101
+ * **ref:** accept only letters, digits, dots, dashes, and underscores in a ref's deck name ([1b430e1](https://github.com/hajimism/dek/commit/1b430e1ea3d0f66af2642a5f257af1cefcae3ac0))
102
+ * **ref:** fold names as macOS does when telling whether two refs share a folder ([fdb32ee](https://github.com/hajimism/dek/commit/fdb32ee103facc5a661f20dabf79fe8f91ae48cd))
103
+ * **ref:** refuse a pin whose folders another ref already takes ([0d550d8](https://github.com/hajimism/dek/commit/0d550d897b0cdad86ac3de181bf7fd031061bd66))
104
+ * refuse a decks folder that links out of the project ([83f2ff0](https://github.com/hajimism/dek/commit/83f2ff00079893b8e2dbb2fc5d5b7588435aa4dd))
105
+ * refuse to serve or write inside a ref, as every other command does ([665b2de](https://github.com/hajimism/dek/commit/665b2de7a3e6aa3b67ce6140d3eb2a15fb398ee6))
106
+ * serve a deck's assets only from inside the project ([86743cf](https://github.com/hajimism/dek/commit/86743cf731d97f664e95b75f1441dd238f0eeb11))
107
+ * take a folder for a ref only where dek wrote one, and name it from there ([62cc8df](https://github.com/hajimism/dek/commit/62cc8df0a509a99a55d9db7698683bc3be5523d4))
108
+ * **theme:** leave layouts whose names are no plain word out of --json ([625f6d3](https://github.com/hajimism/dek/commit/625f6d3dab0dfe16dacb272e371b551214621a86))
109
+ * **theme:** print only layout names that are plain words ([62b15d1](https://github.com/hajimism/dek/commit/62b15d18afb91d6adbc2641376d9ca899cb6bf20))
110
+
111
+
112
+ ### Documentation
113
+
114
+ * say dek follows no link under packs/ either ([5a11b2c](https://github.com/hajimism/dek/commit/5a11b2ca9960a134e261f09f80408fc770aa44da))
115
+
116
+ ## [0.3.4](https://github.com/hajimism/dek/compare/v0.3.3...v0.3.4) (2026-10-27)
117
+
118
+
119
+ ### Features
120
+
121
+ * **pptx:** lay out boxes, pictures, text, the theme, and backgrounds as PowerPoint's own ([#27](https://github.com/hajimism/dek/issues/27)) ([eb854e9](https://github.com/hajimism/dek/commit/eb854e9835508792d3e8cfb9dd4046e8750dab7f))
122
+ * **skills:** teach the review in the browser, and add an optional dek-design skill ([2c18b0e](https://github.com/hajimism/dek/commit/2c18b0e339160fb03ca6ce3915b89e37d82c9522))
123
+ * **skills:** teach the review in the browser, and add an optional dek-design skill ([1b04394](https://github.com/hajimism/dek/commit/1b04394023a239706a2fa29d168811e1a1be05d2))
124
+ * **visual:** report awkward line breaks and edges a few pixels off (DEK035, DEK036) ([#28](https://github.com/hajimism/dek/issues/28)) ([ce75fc1](https://github.com/hajimism/dek/commit/ce75fc14937d76ab457298a5aca9116a0fe3778e))
125
+
126
+
127
+ ### Bug Fixes
128
+
129
+ * refuse a slide script's imports before Bun reads it, so none runs while dek compiles ([2752356](https://github.com/hajimism/dek/commit/275235647869f1e0a040c080476141d3aaa1aaa6))
130
+ * refuse a slide script's imports before Bun reads it, so none runs while dek compiles ([198930c](https://github.com/hajimism/dek/commit/198930c1d41639cc9943f36a3a187ff6eab0f364))
131
+
132
+
133
+ ### Documentation
134
+
135
+ * **adr:** keep dependency code away from the tokens that publish ([103f17c](https://github.com/hajimism/dek/commit/103f17cebb37dd1750caa4cc242acedf55703809))
136
+ * **adr:** renumber the PPTX records after the line-break ones ([ca43352](https://github.com/hajimism/dek/commit/ca4335267b6eeed7131c4123a8a543a230d65875))
137
+ * **adr:** renumber the PPTX records after the line-break ones ([bef6e12](https://github.com/hajimism/dek/commit/bef6e12915ac301843297fb972cf36288bbf612c))
138
+ * **adr:** ship an optional design skill and raise the official skill's floor to 0.2.2 ([6f39dd1](https://github.com/hajimism/dek/commit/6f39dd1e8234297cb0fb72d93722a419d1921f23))
139
+ * name the optional dek-design skill, and the review the official skill teaches ([6908fa1](https://github.com/hajimism/dek/commit/6908fa195e16d79c75d5b77cda72f708798de34c))
140
+
5
141
  ## [0.3.3](https://github.com/hajimism/dek/compare/v0.3.2...v0.3.3) (2026-10-21)
6
142
 
7
143
 
package/README.ja.md CHANGED
@@ -44,7 +44,7 @@ bunx dekc build # 3. dist/2026-04-vite.html — トーク全体がこの 1
44
44
 
45
45
  台本を書いて `dekc` を叩けば、HTML を一行も書かずに同梱テーマで発表できます。
46
46
 
47
- エージェントと作るなら、公式スキルを `npx skills add hajimism/dek` で、Claude Code なら `/plugin marketplace add hajimism/dek` と `/plugin install dek@dek` で入れてください。プロジェクトの外にいるエージェントは [llms.txt](https://hajimism.github.io/dek/llms.txt) から始められます。[AI エージェントと作る](https://hajimism.github.io/dek/ja/guide/ai.html)を参照してください。
47
+ エージェントと作るなら、公式スキルを `npx skills add hajimism/dek --skill dek` で、Claude Code なら `/plugin marketplace add hajimism/dek` と `/plugin install dek@dek` で入れてください。`dek-design` も、同じ方法で選んで入れられます。プロジェクトの外にいるエージェントは [llms.txt](https://hajimism.github.io/dek/llms.txt) から始められます。[AI エージェントと作る](https://hajimism.github.io/dek/ja/guide/ai.html)を参照してください。
48
48
 
49
49
  続きは [はじめる](https://hajimism.github.io/dek/ja/guide/getting-started.html)へ。通しで 1 本作るなら [チュートリアル](https://hajimism.github.io/dek/ja/guide/tutorial.html)。コマンドの一覧は [CLI リファレンス](https://hajimism.github.io/dek/ja/reference/cli.html)にあります。
50
50
 
@@ -64,7 +64,7 @@ bun run dev # または bun run lint:visual / bun run build
64
64
  ```bash
65
65
  bun install
66
66
  bunx playwright install chromium # 最初に一度。実ブラウザを動かすテストに使う
67
- bun test --watch
67
+ bun test --watch --timeout=20000
68
68
  bun run typecheck
69
69
  bun run check # Biome の format + lint
70
70
  bun run docs:dev # VitePress
package/README.md CHANGED
@@ -44,7 +44,7 @@ bunx dekc build # 3. dist/2026-04-vite.html — the whole talk in one file
44
44
 
45
45
  Write the script, run `dekc`, and you can present with the bundled theme without writing a line of HTML.
46
46
 
47
- Working with an agent? Install the official skill with `npx skills add hajimism/dek`, or in Claude Code with `/plugin marketplace add hajimism/dek` and `/plugin install dek@dek`; an agent outside any project can start from [llms.txt](https://hajimism.github.io/dek/llms.txt). See [Working with AI Agents](https://hajimism.github.io/dek/guide/ai.html).
47
+ Working with an agent? Install the official skill with `npx skills add hajimism/dek --skill dek`, or in Claude Code with `/plugin marketplace add hajimism/dek` and `/plugin install dek@dek`; `dek-design` is an optional skill installed the same way; an agent outside any project can start from [llms.txt](https://hajimism.github.io/dek/llms.txt). See [Working with AI Agents](https://hajimism.github.io/dek/guide/ai.html).
48
48
 
49
49
  Continue with [Getting Started](https://hajimism.github.io/dek/guide/getting-started.html), or build a real deck end to end in the [Tutorial](https://hajimism.github.io/dek/guide/tutorial.html). Every command is listed in the [CLI reference](https://hajimism.github.io/dek/reference/cli.html).
50
50
 
@@ -64,7 +64,7 @@ bun run dev # or bun run lint:visual / bun run build
64
64
  ```bash
65
65
  bun install
66
66
  bunx playwright install chromium # once, for the tests that drive a real browser
67
- bun test --watch
67
+ bun test --watch --timeout=20000
68
68
  bun run typecheck
69
69
  bun run check # Biome format + lint
70
70
  bun run docs:dev # VitePress
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hajimism/dek",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "A build system for talks.",
5
5
  "license": "MIT",
6
6
  "author": "Hajime Nakagawa (https://github.com/hajimism)",
@@ -43,8 +43,8 @@
43
43
  "ai-agents"
44
44
  ],
45
45
  "scripts": {
46
- "test": "bun test --parallel=4",
47
- "test:watch": "bun test --watch",
46
+ "test": "bun test --parallel=4 --timeout=20000",
47
+ "test:watch": "bun test --watch --timeout=20000",
48
48
  "typecheck": "tsc --noEmit",
49
49
  "knip": "knip",
50
50
  "schema": "bun scripts/cli-schema.ts",
@@ -64,9 +64,11 @@
64
64
  "@biomejs/biome": "2.5.15",
65
65
  "@happy-dom/global-registrator": "21.0.1",
66
66
  "@types/bun": "^1.4.2",
67
+ "fast-check": "4.10.2",
67
68
  "happy-dom": "21.0.1",
68
69
  "knip": "6.41.0",
69
70
  "playwright": "^1.63.0",
71
+ "publint": "0.3.25",
70
72
  "typescript": "^7.0.2",
71
73
  "vitepress": "^1.6.4"
72
74
  },
@@ -16,7 +16,7 @@ dek builds a talk from its script. `script.md` holds what will be said, in order
16
16
 
17
17
  ## First, ask the dek you have
18
18
 
19
- 1. `bunx dekc --version` and `bunx dekc help --agent`: every command and flag of the dek installed in this project. This file names few on purpose; use only what that list shows.
19
+ 1. `bunx dekc --version` and `bunx dekc help --agent`: every command and flag of the dek installed in this project. This file names few on purpose; use only what that list shows. It is written for dek 0.2.2 or newer; with an older one, follow only that list and `AGENTS.md`.
20
20
  2. Read dek's block in `AGENTS.md`. dek rewrites it for the installed version: the conventions lint checks, how to check a slide, and what to do before reporting a deck as done. Follow it as written.
21
21
  3. In the deck, `bunx dekc theme`: the classes, tokens, and layouts this deck's theme defines, and `bunx dekc theme <layout>` for a layout's markup. Use nothing else.
22
22
 
@@ -39,6 +39,13 @@ A hint that sends a fix to `theme.css` means every slide of the deck shares the
39
39
  - You have looked at `bunx dekc shot --sheet` and judged the deck's balance.
40
40
  - Say what you could not judge, such as the argument and how the timing feels when spoken, and hand it back to the author. Passing lint means nothing measurable is wrong, not that the talk is good.
41
41
 
42
- ## When the human points at something
42
+ ## When the human reviews the deck
43
43
 
44
- If the human marked beats while rehearsing or pointed at elements of a slide, `bunx dekc help --agent` lists the commands that read those notes, and `AGENTS.md` says how to use them. Leave clearing them to the human.
44
+ The human sees what lint cannot. Let them point at it:
45
+
46
+ 1. Start the dev server in the background, since it keeps running: `bunx dekc <deck> --port <free port>`, or your harness's preview tool if it has one.
47
+ 2. Give them the URL and ask them to press `a`, click what they want changed, write a note, and tell you when they are done. They may not know dek: name the keys, not the commands.
48
+ 3. `bunx dekc annotations <deck> --json`, and fix every note. Shoot the slide again with `bunx dekc shot <slug>` and compare it with the note's `shot`. Tell them in short what you changed.
49
+ 4. Ask them to look again, until they say the deck is done. Stop the dev server you started.
50
+
51
+ Leave `dekc annotations clear` to the human. If they marked beats while rehearsing, `AGENTS.md` says how to read those too.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: dek-design
3
+ description: Make dek slides that read at a glance - a heading that states a claim, one focal point, and no visual difference without a meaning. Use when writing or reviewing the look of slides in a dek deck, or when a deck lints clean but still looks off.
4
+ ---
5
+
6
+ # dek-design
7
+
8
+ The audience sees a slide, holds it in mind, then understands it. Make each step cost as little as it can. Lint measures what a rule can decide, such as overflow and contrast; this skill is for what it cannot. Use only the classes, tokens, and layouts `bunx dekc theme` lists.
9
+
10
+ ## Before you start
11
+
12
+ - Projected or read: a talk is spoken, so the words live in `script.md` and a slide shows what the voice cannot. A deck sent to be read alone needs sentences that stand without the speaker.
13
+ - What the audience knows: do not use a term they do not know. If you must, tie it to something they do.
14
+
15
+ ## One slide
16
+
17
+ - The `##` heading is the slide's claim, in one sentence: "Builds fail on cold caches", not "Problems". Read in order, the headings tell the whole talk.
18
+ - The body is the evidence for the heading. Show it as a figure, a chart, or a short table where you can.
19
+ - Keep about four groups on a slide at most. With more, nest them or split the slide.
20
+
21
+ ## How it looks
22
+
23
+ - One thing stands out most on each slide, and it is the main point. Emphasis used everywhere is emphasis nowhere.
24
+ - Change color, type, or layout only when the meaning changes; the audience looks for a reason in every change, so never vary for variety. Mark sections with one layout used the same way each time.
25
+ - When things differ, make them differ clearly. A small difference is not seen as one.
26
+ - Line things up. Items side by side share their top, their bottom, and the line their text starts on; a row of cards is one height even when one holds less. Items stacked share their left edge. An edge a little off reads as a mistake, not a choice, and a shot shows it where markup does not.
27
+ - What sits close, aligns, or shares a color reads as one group. Do not set items of different rank as a row of equal cards.
28
+ - Position means something: time and steps run left to right, more or better goes up, the general sits above the specific. A person or anything with a direction faces into the slide or toward what it points at.
29
+ - The eye goes from the heading at top left, to the body in the middle, to the conclusion or next step at bottom right.
30
+
31
+ ## Charts
32
+
33
+ - A line for a trend, bars to compare values. Show amounts by position or length, not by area, angle, or hue.
34
+ - Label lines and bars directly instead of using a legend. Put each note next to what it explains.
35
+ - No icon or picture that only decorates.
36
+
37
+ ## Check
38
+
39
+ 1. `bunx dekc ls <deck>` lists the headings in order. Read only them: does the talk hold?
40
+ 2. `bunx dekc shot --sheet`, then each slide's own shot: where does the eye land first, and is that the main point? Read `fill` in `bunx dekc check <slug> --json` for where the slide is empty.
41
+ 3. In each slide's shot, edges line up: in a row, tops, bottoms, and the first lines of text; in a column, left edges. Fix any that do not.
42
+ 4. Every difference in position, color, size, and look has a reason you can say. Remove the ones that do not.
@@ -1,7 +1,9 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, statSync } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
+ import { shellName } from "../core/escape.ts";
3
4
  import { isInside } from "../core/path.ts";
4
- import { COMMANDS } from "./commands.ts";
5
+ import { snapshotName } from "../core/ref.ts";
6
+ import { type AnySpec, COMMANDS, subcommandsOf } from "./commands.ts";
5
7
 
6
8
  /** A deck a hint is about: the name a command takes, and the directory that makes it implicit. */
7
9
  export type HintDeck = { name: string; dir: string };
@@ -10,20 +12,72 @@ export type HintDeck = { name: string; dir: string };
10
12
  * A hint's commands as they run from `cwd`. Hints are written as if run inside the deck they are
11
13
  * about, where a command finds its deck by itself. Run anywhere else, a command that takes a deck
12
14
  * needs its name: as the first word outside every deck, and as `--deck` inside another deck,
13
- * where a first word names something in that deck instead. A command that already names a deck,
14
- * or takes none, is left as written.
15
+ * where a first word names something in that deck instead, or where the name would read as a
16
+ * subcommand or a flag. The name is one shell word, however it is spelled, or `<deck>` when no
17
+ * word holds it alike in every shell, with where to find it unless `note` is false, for a hint
18
+ * that joins several and says it once. A command that already names a deck, or takes none, is
19
+ * left as written.
15
20
  */
16
- export function addressHint(hint: string, deck: HintDeck | undefined, cwd: string): string {
21
+ export function addressHint(
22
+ hint: string,
23
+ deck: HintDeck | undefined,
24
+ cwd: string,
25
+ { note: noted = true }: { note?: boolean } = {},
26
+ ): string {
17
27
  if (deck === undefined || isInside(cwd, deck.dir)) {
18
28
  return hint;
19
29
  }
20
- const naming = deckAround(cwd) === undefined ? deck.name : `--deck ${deck.name}`;
21
- return hint.replace(COMMAND_RE, (whole, command: string, rest: string) => {
22
- if (!DECK_COMMANDS.has(command) || namesDeck(rest, deck.name)) {
23
- return whole;
24
- }
25
- return `\`dekc ${command} ${naming}${rest}\``;
26
- });
30
+ const word = shellName(deck.name) ?? DECK_PLACEHOLDER;
31
+ const inOther = deckAround(cwd) !== undefined;
32
+ // The note a placeholder adds names `dekc ls` for the project, not for the deck.
33
+ const note = hint.indexOf(PLACEHOLDER_NOTE);
34
+ let placed = false;
35
+ const addressed = hint.replace(
36
+ COMMAND_RE,
37
+ (whole, command: string, rest: string, offset: number) => {
38
+ const subcommand = SUBCOMMAND_WORDS.get(command)?.has(deck.name) === true;
39
+ // A first word that is a subcommand, or a flag, is never the deck's name.
40
+ const positional = !subcommand && !deck.name.startsWith("-");
41
+ if (
42
+ !DECK_COMMANDS.has(command) ||
43
+ namesDeck(rest, positional ? word : undefined) ||
44
+ (note >= 0 && offset >= note && offset < note + PLACEHOLDER_NOTE.length)
45
+ ) {
46
+ return whole;
47
+ }
48
+ placed ||= word === DECK_PLACEHOLDER;
49
+ const flag = inOther || subcommand;
50
+ return `\`dekc ${command} ${flag ? deckFlag(deck.name) : deckWord(deck.name)}${rest}\``;
51
+ },
52
+ );
53
+ return placed && note < 0 && noted ? `${addressed}${PLACEHOLDER_NOTE}` : addressed;
54
+ }
55
+
56
+ /** `hint` with where to find a deck's name, once, when one of its commands has `<deck>` for it. */
57
+ export function withPlaceholderNote(hint: string): string {
58
+ return hint.includes(DECK_PLACEHOLDER) && !hint.includes(PLACEHOLDER_NOTE)
59
+ ? `${hint}${PLACEHOLDER_NOTE}`
60
+ : hint;
61
+ }
62
+
63
+ /** Where a hint with `<deck>` in it says to find the deck's name. */
64
+ const PLACEHOLDER_NOTE = "; for <deck>, name the deck as `dekc ls` lists it";
65
+
66
+ /** What a command shows for a deck no word holds alike in every shell. */
67
+ const DECK_PLACEHOLDER = "<deck>";
68
+
69
+ /**
70
+ * A deck's name as the first word of a command: one shell word, `<deck>` when there is none, or
71
+ * `--deck=` when the name starts with a dash, which a first word would read as a flag.
72
+ */
73
+ export function deckWord(name: string): string {
74
+ return name.startsWith("-") ? deckFlag(name) : (shellName(name) ?? DECK_PLACEHOLDER);
75
+ }
76
+
77
+ /** A deck's name as `--deck`, joined with `=` when it starts with a dash, as a value must be. */
78
+ export function deckFlag(name: string): string {
79
+ const word = shellName(name) ?? DECK_PLACEHOLDER;
80
+ return name.startsWith("-") ? `--deck=${word}` : `--deck ${word}`;
27
81
  }
28
82
 
29
83
  /** A backquoted command: `dekc`, a command word, and what follows it up to the closing quote. */
@@ -33,20 +87,43 @@ const DECK_COMMANDS = new Set(
33
87
  Object.entries(COMMANDS).flatMap(([name, spec]) => ("scope" in spec && spec.scope ? [name] : [])),
34
88
  );
35
89
 
36
- function namesDeck(rest: string, name: string): boolean {
37
- const [first] = rest.trim().split(/\s+/);
38
- return first === name || first?.includes("/") === true || /(?:^| )--deck(?: |$)/.test(rest);
90
+ /** The first word of each command's subcommands, which a deck's name in its place would read as. */
91
+ const SUBCOMMAND_WORDS = new Map(
92
+ Object.entries(COMMANDS).map(([name, spec]) => [
93
+ name,
94
+ new Set(Object.keys(subcommandsOf(spec as AnySpec)).map((sub) => sub.split(" ")[0])),
95
+ ]),
96
+ );
97
+
98
+ /**
99
+ * Whether a command's words already name a deck: as `word`, the deck's name as one shell word,
100
+ * when the deck can be named first, as a ref, or with --deck.
101
+ */
102
+ function namesDeck(rest: string, word: string | undefined): boolean {
103
+ const words = rest.trim();
104
+ const [first] = words.split(/\s+/);
105
+ return (
106
+ (word !== undefined && (words === word || words.startsWith(`${word} `))) ||
107
+ first?.includes("/") === true ||
108
+ /(?:^| )--deck(?:[ =]|$)/.test(rest)
109
+ );
39
110
  }
40
111
 
41
112
  /**
42
113
  * The deck `path` lies in: the directory under a project's `decks/`, found from the path alone.
43
- * A source path dek reports is absolute, so no project needs to be resolved to address it.
114
+ * A source path dek reports is absolute, so no project needs to be resolved to address it. A
115
+ * deck that is not there, such as the one a `deck "nope" not found` points at, is none, and a
116
+ * ref's deck, in a snapshot as dek writes one, is named by the ref, as every command that reads
117
+ * one takes it.
44
118
  */
45
119
  export function deckAround(path: string): HintDeck | undefined {
46
120
  for (let dir = path; dirname(dir) !== dir; dir = dirname(dir)) {
47
121
  const parent = dirname(dir);
48
- if (basename(parent) === "decks" && existsSync(join(dirname(parent), "dek.toml"))) {
49
- return { name: basename(dir), dir };
122
+ const root = dirname(parent);
123
+ if (basename(parent) === "decks" && existsSync(join(root, "dek.toml"))) {
124
+ return statSync(dir, { throwIfNoEntry: false })?.isDirectory()
125
+ ? { name: snapshotName(root) ?? basename(dir), dir }
126
+ : undefined;
50
127
  }
51
128
  }
52
129
  return undefined;
package/src/cli/build.ts CHANGED
@@ -5,7 +5,7 @@ import { lintDeck, lintProject } from "../core/lint.ts";
5
5
  import { parsePublicUrl } from "../core/ogp.ts";
6
6
  import { PLAYWRIGHT_INSTALL, type PlaywrightRunner } from "../core/playwright.ts";
7
7
  import { playerScript } from "../runtime/player.ts";
8
- import { type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
8
+ import { RENDERED_RULES, type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
9
9
  import type { DecksTarget } from "./scope.ts";
10
10
 
11
11
  /**
@@ -77,7 +77,7 @@ export async function buildCommand(
77
77
  ...skippedChecks([
78
78
  ...skipped,
79
79
  visualSkipped("dekc lint --visual", {
80
- reason: "overflow and contrast are measured only by `dekc lint --visual`",
80
+ reason: `${RENDERED_RULES} are measured only by \`dekc lint --visual\``,
81
81
  ...(options.runner ? { runner: options.runner } : {}),
82
82
  }),
83
83
  ]),
package/src/cli/check.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Diagnostic } from "../core/diagnostic.ts";
2
2
  import { DekError } from "../core/error.ts";
3
+ import { shellQuote } from "../core/escape.ts";
3
4
  import type { Fill } from "../core/fill.ts";
4
5
  import { lintDeck } from "../core/lint.ts";
5
6
  import { type PlaywrightRunner, playwrightMissingError } from "../core/playwright.ts";
@@ -53,7 +54,7 @@ export async function checkCommand(
53
54
 
54
55
  const skipped: SkippedCheck[] = visual
55
56
  ? []
56
- : [visualSkipped(`dekc check ${slug}`, { reason: "Playwright is not installed" })];
57
+ : [visualSkipped(`dekc check ${shellQuote(slug)}`, { reason: "Playwright is not installed" })];
57
58
 
58
59
  // A live-only deck is done without voice, so asking for it skips the check rather than failing,
59
60
  // the way a missing Playwright skips visual.