@prestack/thread-ui 0.1.9 → 0.1.11

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 (210) hide show
  1. package/CHANGELOG.md +366 -0
  2. package/dist/ui/attach.js +6 -7
  3. package/dist/ui/compositions.d.ts +0 -2
  4. package/dist/ui/compositions.js +2 -112
  5. package/dist/ui/generators/routes.js +1 -0
  6. package/dist/ui/variant.d.ts +1 -3
  7. package/package.json +2 -2
  8. package/dist/ui/animations/anm_appear.ts +0 -13
  9. package/dist/ui/animations/anm_blur_in.ts +0 -19
  10. package/dist/ui/animations/anm_blur_out.ts +0 -18
  11. package/dist/ui/animations/anm_dismiss.ts +0 -18
  12. package/dist/ui/animations/anm_drop.ts +0 -19
  13. package/dist/ui/animations/anm_fall.ts +0 -18
  14. package/dist/ui/animations/anm_flash.ts +0 -18
  15. package/dist/ui/animations/anm_glow.ts +0 -16
  16. package/dist/ui/animations/anm_lift.ts +0 -16
  17. package/dist/ui/animations/anm_nudge.ts +0 -16
  18. package/dist/ui/animations/anm_pop_in.ts +0 -23
  19. package/dist/ui/animations/anm_pop_out.ts +0 -22
  20. package/dist/ui/animations/anm_press.ts +0 -12
  21. package/dist/ui/animations/anm_pulse.ts +0 -13
  22. package/dist/ui/animations/anm_rise.ts +0 -19
  23. package/dist/ui/animations/anm_shake.ts +0 -13
  24. package/dist/ui/animations/anm_slide_in_left.ts +0 -19
  25. package/dist/ui/animations/anm_slide_in_right.ts +0 -19
  26. package/dist/ui/animations/anm_slide_out_left.ts +0 -18
  27. package/dist/ui/animations/anm_slide_out_right.ts +0 -18
  28. package/dist/ui/animations/anm_spin.ts +0 -12
  29. package/dist/ui/animations/anm_swell.ts +0 -16
  30. package/dist/ui/animations/anm_wobble.ts +0 -24
  31. package/dist/ui/animations/anm_zoom_in.ts +0 -19
  32. package/dist/ui/animations/anm_zoom_out.ts +0 -18
  33. package/dist/ui/animations.ts +0 -308
  34. package/dist/ui/attach.ts +0 -553
  35. package/dist/ui/charts.tsx +0 -69
  36. package/dist/ui/checked.ts +0 -32
  37. package/dist/ui/collector.ts +0 -140
  38. package/dist/ui/compositions.ts +0 -555
  39. package/dist/ui/consent.ts +0 -55
  40. package/dist/ui/content.tsx +0 -27
  41. package/dist/ui/contrast.ts +0 -27
  42. package/dist/ui/controls.tsx +0 -25
  43. package/dist/ui/disclosure.tsx +0 -20
  44. package/dist/ui/dismiss-layer.ts +0 -87
  45. package/dist/ui/door.ts +0 -140
  46. package/dist/ui/drag.ts +0 -316
  47. package/dist/ui/elements/accordion.tsx +0 -47
  48. package/dist/ui/elements/accordion.works.ts +0 -66
  49. package/dist/ui/elements/aspect-ratio.tsx +0 -31
  50. package/dist/ui/elements/aspect-ratio.works.ts +0 -5
  51. package/dist/ui/elements/avatar.tsx +0 -34
  52. package/dist/ui/elements/avatar.works.ts +0 -16
  53. package/dist/ui/elements/badge.tsx +0 -33
  54. package/dist/ui/elements/badge.works.ts +0 -9
  55. package/dist/ui/elements/bars.tsx +0 -50
  56. package/dist/ui/elements/bars.works.ts +0 -29
  57. package/dist/ui/elements/box.tsx +0 -36
  58. package/dist/ui/elements/breadcrumb.tsx +0 -38
  59. package/dist/ui/elements/breadcrumb.works.ts +0 -24
  60. package/dist/ui/elements/button.tsx +0 -46
  61. package/dist/ui/elements/button.works.tsx +0 -52
  62. package/dist/ui/elements/column.tsx +0 -29
  63. package/dist/ui/elements/consent.tsx +0 -71
  64. package/dist/ui/elements/consent.works.tsx +0 -132
  65. package/dist/ui/elements/donut.tsx +0 -45
  66. package/dist/ui/elements/donut.works.ts +0 -42
  67. package/dist/ui/elements/each.tsx +0 -34
  68. package/dist/ui/elements/each.works.tsx +0 -82
  69. package/dist/ui/elements/empty.tsx +0 -33
  70. package/dist/ui/elements/field.tsx +0 -45
  71. package/dist/ui/elements/field.works.ts +0 -65
  72. package/dist/ui/elements/funnel.tsx +0 -50
  73. package/dist/ui/elements/funnel.works.ts +0 -37
  74. package/dist/ui/elements/gauge.tsx +0 -40
  75. package/dist/ui/elements/gauge.works.ts +0 -42
  76. package/dist/ui/elements/glyph.tsx +0 -30
  77. package/dist/ui/elements/glyph.works.tsx +0 -42
  78. package/dist/ui/elements/grid.tsx +0 -29
  79. package/dist/ui/elements/heatmap.tsx +0 -48
  80. package/dist/ui/elements/heatmap.works.ts +0 -24
  81. package/dist/ui/elements/hit-box.tsx +0 -47
  82. package/dist/ui/elements/hit-box.works.tsx +0 -576
  83. package/dist/ui/elements/image.tsx +0 -29
  84. package/dist/ui/elements/line-chart.tsx +0 -45
  85. package/dist/ui/elements/line-chart.works.ts +0 -41
  86. package/dist/ui/elements/link.tsx +0 -38
  87. package/dist/ui/elements/link.works.ts +0 -25
  88. package/dist/ui/elements/media.tsx +0 -57
  89. package/dist/ui/elements/menu.tsx +0 -51
  90. package/dist/ui/elements/menu.works.tsx +0 -154
  91. package/dist/ui/elements/money.tsx +0 -36
  92. package/dist/ui/elements/money.works.ts +0 -9
  93. package/dist/ui/elements/pagination.tsx +0 -39
  94. package/dist/ui/elements/pagination.works.ts +0 -20
  95. package/dist/ui/elements/progress.tsx +0 -34
  96. package/dist/ui/elements/progress.works.ts +0 -8
  97. package/dist/ui/elements/radio-group.tsx +0 -50
  98. package/dist/ui/elements/radio-group.works.ts +0 -33
  99. package/dist/ui/elements/row.tsx +0 -29
  100. package/dist/ui/elements/scatter.tsx +0 -42
  101. package/dist/ui/elements/scatter.works.ts +0 -38
  102. package/dist/ui/elements/select.tsx +0 -43
  103. package/dist/ui/elements/select.works.ts +0 -5
  104. package/dist/ui/elements/separator.tsx +0 -31
  105. package/dist/ui/elements/skeleton.tsx +0 -31
  106. package/dist/ui/elements/slider.tsx +0 -34
  107. package/dist/ui/elements/slider.works.ts +0 -5
  108. package/dist/ui/elements/sparkline.tsx +0 -37
  109. package/dist/ui/elements/sparkline.works.ts +0 -32
  110. package/dist/ui/elements/spinner.tsx +0 -33
  111. package/dist/ui/elements/stack.tsx +0 -29
  112. package/dist/ui/elements/stat.tsx +0 -36
  113. package/dist/ui/elements/stat.works.ts +0 -14
  114. package/dist/ui/elements/surface.tsx +0 -36
  115. package/dist/ui/elements/surface.works.ts +0 -9
  116. package/dist/ui/elements/table.tsx +0 -62
  117. package/dist/ui/elements/table.works.ts +0 -30
  118. package/dist/ui/elements/tabs.tsx +0 -47
  119. package/dist/ui/elements/tabs.works.ts +0 -84
  120. package/dist/ui/elements/text.tsx +0 -33
  121. package/dist/ui/elements/textarea.tsx +0 -38
  122. package/dist/ui/elements/textarea.works.ts +0 -5
  123. package/dist/ui/elements/time.tsx +0 -40
  124. package/dist/ui/elements/time.works.ts +0 -29
  125. package/dist/ui/elements/toggle.tsx +0 -34
  126. package/dist/ui/elements/toggle.works.ts +0 -5
  127. package/dist/ui/elements/tooltip.tsx +0 -40
  128. package/dist/ui/elements/tooltip.works.ts +0 -77
  129. package/dist/ui/elements/value.tsx +0 -38
  130. package/dist/ui/elements/value.works.ts +0 -9
  131. package/dist/ui/elements.ts +0 -127
  132. package/dist/ui/focus-scope.ts +0 -121
  133. package/dist/ui/fonts.ts +0 -355
  134. package/dist/ui/generators/dev.d.ts +0 -1
  135. package/dist/ui/generators/dev.js +0 -61
  136. package/dist/ui/generators/dev.ts +0 -74
  137. package/dist/ui/generators/elements.ts +0 -796
  138. package/dist/ui/generators/i18n.ts +0 -283
  139. package/dist/ui/generators/icons.ts +0 -207
  140. package/dist/ui/generators/react.ts +0 -267
  141. package/dist/ui/generators/routes.ts +0 -847
  142. package/dist/ui/generators/show.ts +0 -261
  143. package/dist/ui/generators/styles.ts +0 -677
  144. package/dist/ui/generators/variants.ts +0 -196
  145. package/dist/ui/i18n/catalogs/en.ts +0 -191
  146. package/dist/ui/i18n/format.ts +0 -221
  147. package/dist/ui/i18n/locales.ts +0 -33
  148. package/dist/ui/i18n/parse.ts +0 -315
  149. package/dist/ui/i18n/translator.ts +0 -31
  150. package/dist/ui/i18n/types.ts +0 -96
  151. package/dist/ui/icons.ts +0 -331
  152. package/dist/ui/islands/map.ts +0 -58
  153. package/dist/ui/localizations.ts +0 -75
  154. package/dist/ui/material.ts +0 -236
  155. package/dist/ui/materials/mat_bordered.ts +0 -13
  156. package/dist/ui/materials/mat_ceramic.ts +0 -13
  157. package/dist/ui/materials/mat_clay.ts +0 -13
  158. package/dist/ui/materials/mat_elevated.ts +0 -24
  159. package/dist/ui/materials/mat_flat.ts +0 -34
  160. package/dist/ui/materials/mat_glass.ts +0 -20
  161. package/dist/ui/materials/mat_lit.ts +0 -13
  162. package/dist/ui/materials/mat_moulded.ts +0 -11
  163. package/dist/ui/materials.ts +0 -197
  164. package/dist/ui/modes.ts +0 -44
  165. package/dist/ui/navigation.tsx +0 -29
  166. package/dist/ui/positioning.ts +0 -90
  167. package/dist/ui/presented.tsx +0 -119
  168. package/dist/ui/provider.tsx +0 -184
  169. package/dist/ui/render-loop.ts +0 -133
  170. package/dist/ui/roving-focus.ts +0 -161
  171. package/dist/ui/screens.ts +0 -141
  172. package/dist/ui/scroll-lock.ts +0 -28
  173. package/dist/ui/series.ts +0 -97
  174. package/dist/ui/signals.ts +0 -386
  175. package/dist/ui/styleguide.ts +0 -231
  176. package/dist/ui/styleguides.ts +0 -374
  177. package/dist/ui/themes.ts +0 -22
  178. package/dist/ui/token.ts +0 -78
  179. package/dist/ui/tokens/shadow.ts +0 -25
  180. package/dist/ui/tokens.ts +0 -234
  181. package/dist/ui/validators.ts +0 -232
  182. package/dist/ui/variant.ts +0 -352
  183. package/dist/ui/variants/var_avatar-round.ts +0 -14
  184. package/dist/ui/variants/var_bubble-me.ts +0 -14
  185. package/dist/ui/variants/var_bubble-them.ts +0 -13
  186. package/dist/ui/variants/var_button-border.ts +0 -76
  187. package/dist/ui/variants/var_button-cta.ts +0 -76
  188. package/dist/ui/variants/var_button-destructive-ghost.ts +0 -72
  189. package/dist/ui/variants/var_button-destructive.ts +0 -76
  190. package/dist/ui/variants/var_button-fill.ts +0 -76
  191. package/dist/ui/variants/var_button-ghost.ts +0 -72
  192. package/dist/ui/variants/var_button-glass.ts +0 -55
  193. package/dist/ui/variants/var_button-normal.ts +0 -76
  194. package/dist/ui/variants/var_button-quiet.ts +0 -74
  195. package/dist/ui/variants/var_card.ts +0 -17
  196. package/dist/ui/variants/var_chip.ts +0 -13
  197. package/dist/ui/variants/var_choice.ts +0 -15
  198. package/dist/ui/variants/var_framed.ts +0 -10
  199. package/dist/ui/variants/var_glyph-plain.ts +0 -11
  200. package/dist/ui/variants/var_input.ts +0 -43
  201. package/dist/ui/variants/var_link-inline.ts +0 -29
  202. package/dist/ui/variants/var_list-row.ts +0 -34
  203. package/dist/ui/variants/var_rule.ts +0 -10
  204. package/dist/ui/variants/var_skeleton-block.ts +0 -12
  205. package/dist/ui/variants/var_spinner-turning.ts +0 -12
  206. package/dist/ui/variants/var_stat-tile.ts +0 -13
  207. package/dist/ui/variants/var_tab-bar.ts +0 -15
  208. package/dist/ui/variants/var_tap-target.ts +0 -25
  209. package/dist/ui/variants.ts +0 -64
  210. package/dist/ui/wrappers.tsx +0 -429
@@ -1,18 +0,0 @@
1
- import type { Animation } from '../animations.ts'
2
-
3
- export const ZOOM_OUT = {
4
- id: 'zoom-out',
5
- runs: 'motion-quick',
6
- eases: 'ease-in',
7
- fills: 'forwards',
8
- stops: {
9
- '0%': [
10
- { property: 'opacity', is: 'opacity-full' },
11
- { property: 'transform', is: 'transform-rest' },
12
- ],
13
- '100%': [
14
- { property: 'opacity', is: 'opacity-none' },
15
- { property: 'transform', is: 'transform-grow' },
16
- ],
17
- },
18
- } as const satisfies Animation
@@ -1,308 +0,0 @@
1
- import { existsSync, writeFileSync } from '@prestack/thread/integrations/disk'
2
- import { basename, join } from 'node:path'
3
- import { argv, stdout } from 'node:process'
4
- import { pathToFileURL } from 'node:url'
5
-
6
- import { exit, invoked } from '@prestack/thread/core/invoked'
7
- import type { Threaded } from '@prestack/thread/core/thread'
8
- import { findTree, inTree, strayFiles } from '@prestack/thread/core/tree'
9
-
10
- import { held, sheeted, type Holds, type Token } from './token.ts'
11
-
12
- const here = 'thread/ui/animations.ts'
13
-
14
- export const BOUND = {
15
- opacity: ['opacity'],
16
- transform: ['transform'],
17
- filter: ['filter'],
18
- backdropFilter: ['blur'],
19
- background: ['colour', 'gradient'],
20
- color: ['colour'],
21
- borderColor: ['colour'],
22
- boxShadow: ['shadow'],
23
- } as const
24
-
25
- type Property = keyof typeof BOUND
26
-
27
- type SettingFor<P extends Property> = {
28
- readonly property: P
29
- readonly is?: string
30
- readonly token?: (typeof BOUND)[P][number]
31
- readonly asks?: Readonly<Record<string, string | number>>
32
- }
33
-
34
- export type AnimationSetting = { [P in Property]: SettingFor<P> }[Property]
35
-
36
- export interface Animation {
37
- readonly id: string
38
-
39
- readonly runs: string
40
-
41
- readonly eases: string
42
-
43
- readonly repeats?: number | 'infinite'
44
-
45
- readonly fills?: 'none' | 'forwards' | 'backwards' | 'both'
46
-
47
- readonly stops: Readonly<Record<string, readonly AnimationSetting[]>>
48
- }
49
-
50
- export function keyframeName(id: string): string {
51
- return `anm-${id}`
52
- }
53
-
54
- function resolve(
55
- animation: Animation,
56
- set: AnimationSetting,
57
- value: string,
58
- named: ReadonlySet<string>,
59
- kinds: Readonly<Record<string, string>>,
60
- ): string {
61
- if (!named.has(value)) {
62
- throw new Error(
63
- `\`${animation.id}\` sets ${set.property} to "${value}", and the style guide holds no row by ` +
64
- 'that name. An animation composes what the style guide holds, or it composes nothing.',
65
- )
66
- }
67
- const kind = kinds[value]
68
- const accepted: readonly string[] = BOUND[set.property]
69
- if (kind !== undefined && !accepted.includes(kind)) {
70
- throw new Error(
71
- `\`${animation.id}\` sets ${set.property} to \`${value}\`, and that row is a \`${kind}\`. ` +
72
- `${set.property} takes ${accepted.map((one) => `\`${one}\``).join(' or ')}.`,
73
- )
74
- }
75
- return `var(--${value})`
76
- }
77
-
78
- function valued(
79
- animation: Animation,
80
- set: AnimationSetting,
81
- named: ReadonlySet<string>,
82
- tokens: Readonly<Record<string, Token>>,
83
- kinds: Readonly<Record<string, string>>,
84
- ): Holds {
85
- if (set.token !== undefined) {
86
- const token = tokens[set.token]
87
- if (token?.makes === undefined) {
88
- throw new Error(
89
- `\`${animation.id}\` asks \`${set.token}\` for ${set.property}, and that token carries no rule ` +
90
- 'to make a value from. Name a style guide row instead, or give the token a rule.',
91
- )
92
- }
93
- return sheeted(set.token, token.makes(set.asks ?? {}))
94
- }
95
- if (set.is === undefined) {
96
- throw new Error(
97
- `\`${animation.id}\` states ${set.property} with neither a value nor a token to make one from.`,
98
- )
99
- }
100
- return held(named, resolve(animation, set, set.is, named, kinds), animation.id)
101
- }
102
-
103
- export function stopsOf(
104
- animation: Animation,
105
- named: ReadonlySet<string>,
106
- tokens: Readonly<Record<string, Token>>,
107
- kinds: Readonly<Record<string, string>>,
108
- ): readonly { readonly stop: string; readonly made: Readonly<Record<string, Holds>> }[] {
109
- return Object.keys(animation.stops).map((stop) => {
110
- const made: Record<string, Holds> = {}
111
- for (const set of animation.stops[stop] ?? []) made[set.property] = valued(animation, set, named, tokens, kinds)
112
- return { stop, made }
113
- })
114
- }
115
-
116
- export function shorthandOf(animation: Animation, named: ReadonlySet<string>): string {
117
- for (const [what, row] of [
118
- ['runs', animation.runs],
119
- ['eases', animation.eases],
120
- ] as const) {
121
- if (named.has(row)) continue
122
- throw new Error(
123
- `\`${animation.id}\` ${what} \`${row}\`, and the style guide holds no row by that name. ` +
124
- 'How long an animation takes and how it is paced are style guide values, named here rather than written.',
125
- )
126
- }
127
- return [
128
- keyframeName(animation.id),
129
- `var(--${animation.runs})`,
130
- `var(--${animation.eases})`,
131
- ...(animation.repeats === undefined ? [] : [String(animation.repeats)]),
132
- ...(animation.fills === undefined ? [] : [animation.fills]),
133
- ].join(' ')
134
- }
135
-
136
- const one = (id: string, describes: string): Threaded => ({
137
- id,
138
- at: `thread/ui/animations/anm_${id.replace(/-/g, '_')}.ts`,
139
- describes,
140
- version: 1,
141
- })
142
-
143
- export const declares: readonly Threaded[] = [
144
- one('press', 'A thing shrinking very slightly and coming back, which is what makes a press feel like it landed rather than like a value changed.'),
145
- one('appear', 'Something arriving in place — faded up from nothing, without moving, for a thing that was always going to be there.'),
146
- one('dismiss', 'Something leaving — faded away and shrunk a little, so the eye is not asked to follow it out.'),
147
- one('rise', 'Something arriving from below, faded up as it comes. What a sheet, a toast or a menu does on its way in.'),
148
- one('fall', 'Something leaving downward, the exit that answers a rise.'),
149
- one('pulse', 'A thing breathing in place, repeated without end, for something waiting rather than working.'),
150
- one('spin', 'A full turn, repeated without end. What says a thing is working and cannot say how far along it is.'),
151
- one('drop', 'Something arriving from above, faded in as it comes. The entrance that answers a rise.'),
152
- one('slide-in-left', 'Something coming in from the left, for a panel or a step arriving from off-stage rather than growing in place.'),
153
- one('slide-in-right', 'Something coming in from the right.'),
154
- one('slide-out-left', 'Something leaving to the left.'),
155
- one('slide-out-right', 'Something leaving to the right.'),
156
- one('zoom-in', 'Something arriving from nearer the reader and settling back to its true size.'),
157
- one('zoom-out', 'Something leaving toward the reader as it fades.'),
158
- one('pop-in', 'Something arriving past its own size and coming back — the overshoot is what reads as physical rather than as a value changing.'),
159
- one('pop-out', 'Something swelling once before it goes, the exit that answers a pop.'),
160
- one('blur-in', 'Something sharpening into place out of softness, without moving at all.'),
161
- one('blur-out', 'Something dissolving rather than leaving.'),
162
- one('lift', 'A thing raised a little off the page while it is pointed at. The plainest way a surface says it is reachable.'),
163
- one('swell', 'A thing grown very slightly while pointed at, for where lifting it would move its neighbours.'),
164
- one('nudge', 'A marker leaning the way it would take you, played while the wording beside it is pointed at.'),
165
- one('wobble', 'A thing turned one way and back to be noticed. Says look at me without saying something is wrong.'),
166
- one('flash', 'A thing dipping and returning, for a value that changed underneath the reader.'),
167
- one('glow', 'The light around a thing swelling and settling, for something lit rather than framed.'),
168
- one('shake', 'A short refusal — a thing pushed off centre and back twice, for an answer that was not accepted.'),
169
- ]
170
-
171
- export const ANIMATIONS = {
172
- id: 'animation',
173
- at: here,
174
- files: 'ui/anm_*.ts',
175
- describes:
176
- 'Every animation an app may play, and the only place one may be brought into existence. A row names a file prefixed `anm_`, and that file is the animation: the stops it moves through, how long it takes and how it is paced — all of it naming style guide rows rather than holding values, so an animation moves when the rows move. Naming one is the whole of using one: a variant that plays `press` says `press`, and states no duration, no easing and no keyframes. The style guide picks which animation is the default for a thing, the way it picks every other value. A file under animations that no row names is not an animation but a file somebody wrote.',
177
- version: 1,
178
- cites: [
179
- {
180
- on: '2026-08-28',
181
- said: 'Animations.ts, declares rows (through threaded) that names animations files at a path, with a prefix anm_ or ani_ or we do motions.ts with mot_, don\'t know. These define a bunch of animations that style guide will pick for a bunch of things as defaults. Like these template animations we see in video editors, we just name them and it\'s done.',
182
- },
183
- ],
184
- declares,
185
- } satisfies Threaded & { readonly files: string; readonly declares: readonly Threaded[] }
186
-
187
- export function nameOf(id: string): string {
188
- return id.replace(/-/g, '_').toUpperCase()
189
- }
190
-
191
- function starter(row: Threaded): string {
192
- return `import type { Animation } from './animations.ts'
193
-
194
- export const ${nameOf(row.id)} = {
195
- id: '${row.id}',
196
- runs: 'motion-normal',
197
- eases: 'ease-out',
198
- stops: {},
199
- } as const satisfies Animation
200
- `
201
- }
202
-
203
- export function seeds(
204
- rows: readonly Threaded[],
205
- has: (at: string) => boolean,
206
- ): readonly { readonly path: string; readonly content: string }[] {
207
- return rows.filter((row) => !has(row.at)).map((row) => ({ path: row.at, content: starter(row) }))
208
- }
209
-
210
- export async function mismatched(
211
- rows: readonly Threaded[],
212
- loader: (row: Threaded) => Promise<{ readonly id?: string } | undefined>,
213
- ): Promise<readonly string[]> {
214
- const found: string[] = []
215
- for (const row of rows) {
216
- const animation = await loader(row)
217
- if (animation === undefined) {
218
- found.push(
219
- `\`${row.id}\` is declared at \`${row.at}\`, and that file exports no \`${nameOf(row.id)}\`. ` +
220
- 'A row names a file, and the file is the thing.',
221
- )
222
- continue
223
- }
224
- if (animation.id !== row.id) {
225
- found.push(
226
- `\`${row.at}\` declares itself \`${String(animation.id)}\`, and the row that names it calls it \`${row.id}\`. ` +
227
- "An animation file and the row that names it have to be the same animation — make the file's `id` match the row, or fix the row.",
228
- )
229
- }
230
- }
231
- return found
232
- }
233
-
234
- export function orphaned(rows: readonly Threaded[], strays: readonly string[]): readonly string[] {
235
- const named = new Set(rows.map((row) => row.at))
236
- return strays
237
- .filter((one) => !named.has(one))
238
- .map(
239
- (one) =>
240
- `\`${one}\` is under animations and no row names it — not a animation, a file somebody wrote. ` +
241
- 'Add a row for it, or delete the file.',
242
- )
243
- }
244
-
245
- async function run(): Promise<number> {
246
- const tree = findTree()
247
- const at = inTree(tree, 'animations.ts')
248
- if (!existsSync(at)) {
249
- stdout.write(`thread: this tree has no ${tree.name}/${basename(at)}, so it declares no animation and there is nothing to seed or check.\n`)
250
- return 0
251
- }
252
- const sheet = (await import(pathToFileURL(at).href)) as {
253
- ANIMATION?: { readonly declares?: readonly Threaded[] }
254
- }
255
-
256
- if (sheet.ANIMATION === undefined) {
257
- stdout.write(`thread: ${tree.name}/animations.ts exports no \`ANIMATION\`, so nothing declares an animation here.\n`)
258
- return 1
259
- }
260
-
261
- const rows = sheet.ANIMATION.declares ?? []
262
- const checking = argv.includes('--check')
263
- const exists = (where: string): boolean => existsSync(join(tree.at, where))
264
- const made = seeds(rows, exists)
265
-
266
- if (checking) {
267
- const unseeded = rows.find((row) => !exists(row.at))
268
- if (unseeded !== undefined) {
269
- stdout.write(
270
- `thread: \`${unseeded.id}\` is declared at \`${unseeded.at}\`, and the file is not there yet. ` +
271
- 'A row names a file, and `--check` never writes one — run `thread animations` and commit what it seeds.\n',
272
- )
273
- return 1
274
- }
275
- } else {
276
- for (const seed of made) {
277
- writeFileSync(join(tree.at, seed.path), seed.content, 'utf8')
278
- stdout.write(`thread: seeded ${seed.path}\n`)
279
- }
280
- }
281
-
282
- const wrong = (
283
- await mismatched(rows, async (row) => {
284
- const module = (await import(pathToFileURL(join(tree.at, row.at)).href)) as Record<string, unknown>
285
- return module[nameOf(row.id)] as { readonly id?: string } | undefined
286
- })
287
- )[0]
288
- if (wrong !== undefined) {
289
- stdout.write(`thread: ${wrong}\n`)
290
- return 1
291
- }
292
-
293
- const stray = orphaned(rows, strayFiles(tree.at, 'anm_', '.' + 'ts'))[0]
294
- if (stray !== undefined) {
295
- stdout.write(`thread: ${stray}\n`)
296
- return 1
297
- }
298
-
299
- stdout.write(
300
- checking
301
- ? `thread: ${String(rows.length)} animation(s) declared, all seeded\n`
302
- : `thread: ${String(rows.length)} animation(s) declared, ${String(made.length)} seeded, ` +
303
- `${String(rows.length - made.length)} already written\n`,
304
- )
305
- return 0
306
- }
307
-
308
- if (invoked(import.meta.url)) exit(await run())