@prestack/thread-ui 0.0.1 → 0.0.2
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.
- package/dist/ui/animations/anm_appear.ts +13 -0
- package/dist/ui/animations/anm_blur_in.ts +19 -0
- package/dist/ui/animations/anm_blur_out.ts +18 -0
- package/dist/ui/animations/anm_dismiss.ts +18 -0
- package/dist/ui/animations/anm_drop.ts +19 -0
- package/dist/ui/animations/anm_fall.ts +18 -0
- package/dist/ui/animations/anm_flash.ts +18 -0
- package/dist/ui/animations/anm_glow.ts +16 -0
- package/dist/ui/animations/anm_lift.ts +16 -0
- package/dist/ui/animations/anm_nudge.ts +16 -0
- package/dist/ui/animations/anm_pop_in.ts +23 -0
- package/dist/ui/animations/anm_pop_out.ts +22 -0
- package/dist/ui/animations/anm_press.ts +12 -0
- package/dist/ui/animations/anm_pulse.ts +13 -0
- package/dist/ui/animations/anm_rise.ts +19 -0
- package/dist/ui/animations/anm_shake.ts +13 -0
- package/dist/ui/animations/anm_slide_in_left.ts +19 -0
- package/dist/ui/animations/anm_slide_in_right.ts +19 -0
- package/dist/ui/animations/anm_slide_out_left.ts +18 -0
- package/dist/ui/animations/anm_slide_out_right.ts +18 -0
- package/dist/ui/animations/anm_spin.ts +12 -0
- package/dist/ui/animations/anm_swell.ts +16 -0
- package/dist/ui/animations/anm_wobble.ts +24 -0
- package/dist/ui/animations/anm_zoom_in.ts +19 -0
- package/dist/ui/animations/anm_zoom_out.ts +18 -0
- package/dist/ui/animations.ts +317 -0
- package/dist/ui/attach.js +13 -4
- package/dist/ui/attach.ts +613 -0
- package/dist/ui/charts.tsx +74 -0
- package/dist/ui/checked.d.ts +2 -2
- package/dist/ui/checked.ts +32 -0
- package/dist/ui/collector.ts +140 -0
- package/dist/ui/compositions.d.ts +10 -1
- package/dist/ui/compositions.js +262 -23
- package/dist/ui/compositions.ts +640 -0
- package/dist/ui/consent.ts +55 -0
- package/dist/ui/content.tsx +39 -0
- package/dist/ui/contrast.ts +27 -0
- package/dist/ui/controls.tsx +30 -0
- package/dist/ui/disclosure.tsx +22 -0
- package/dist/ui/dismiss-layer.ts +87 -0
- package/dist/ui/door.ts +140 -0
- package/dist/ui/drag.ts +316 -0
- package/dist/ui/elements/accordion.js +2 -2
- package/dist/ui/elements/accordion.tsx +47 -0
- package/dist/ui/elements/accordion.works.ts +66 -0
- package/dist/ui/elements/aspect-ratio.js +1 -1
- package/dist/ui/elements/aspect-ratio.tsx +30 -0
- package/dist/ui/elements/aspect-ratio.works.ts +5 -0
- package/dist/ui/elements/avatar.js +1 -1
- package/dist/ui/elements/avatar.tsx +33 -0
- package/dist/ui/elements/avatar.works.ts +16 -0
- package/dist/ui/elements/badge.js +1 -1
- package/dist/ui/elements/badge.tsx +32 -0
- package/dist/ui/elements/badge.works.ts +9 -0
- package/dist/ui/elements/bars.js +2 -2
- package/dist/ui/elements/bars.tsx +49 -0
- package/dist/ui/elements/bars.works.ts +29 -0
- package/dist/ui/elements/box.js +3 -2
- package/dist/ui/elements/box.tsx +35 -0
- package/dist/ui/elements/breadcrumb.js +2 -2
- package/dist/ui/elements/breadcrumb.tsx +37 -0
- package/dist/ui/elements/breadcrumb.works.ts +24 -0
- package/dist/ui/elements/button.js +1 -1
- package/dist/ui/elements/button.tsx +45 -0
- package/dist/ui/elements/button.works.tsx +52 -0
- package/dist/ui/elements/column.js +1 -1
- package/dist/ui/elements/column.tsx +28 -0
- package/dist/ui/elements/consent.js +2 -2
- package/dist/ui/elements/consent.tsx +70 -0
- package/dist/ui/elements/consent.works.tsx +132 -0
- package/dist/ui/elements/donut.js +2 -2
- package/dist/ui/elements/donut.tsx +44 -0
- package/dist/ui/elements/donut.works.ts +42 -0
- package/dist/ui/elements/each.js +1 -1
- package/dist/ui/elements/each.tsx +33 -0
- package/dist/ui/elements/each.works.tsx +82 -0
- package/dist/ui/elements/empty.js +1 -1
- package/dist/ui/elements/empty.tsx +32 -0
- package/dist/ui/elements/field.js +1 -1
- package/dist/ui/elements/field.tsx +44 -0
- package/dist/ui/elements/field.works.ts +65 -0
- package/dist/ui/elements/funnel.js +2 -2
- package/dist/ui/elements/funnel.tsx +49 -0
- package/dist/ui/elements/funnel.works.ts +37 -0
- package/dist/ui/elements/gauge.js +1 -1
- package/dist/ui/elements/gauge.tsx +39 -0
- package/dist/ui/elements/gauge.works.ts +42 -0
- package/dist/ui/elements/glyph.js +1 -1
- package/dist/ui/elements/glyph.tsx +29 -0
- package/dist/ui/elements/glyph.works.tsx +42 -0
- package/dist/ui/elements/grid.js +1 -1
- package/dist/ui/elements/grid.tsx +28 -0
- package/dist/ui/elements/heatmap.js +2 -2
- package/dist/ui/elements/heatmap.tsx +47 -0
- package/dist/ui/elements/heatmap.works.ts +24 -0
- package/dist/ui/elements/hit-box.js +1 -1
- package/dist/ui/elements/hit-box.tsx +46 -0
- package/dist/ui/elements/hit-box.works.tsx +576 -0
- package/dist/ui/elements/image.js +1 -1
- package/dist/ui/elements/image.tsx +28 -0
- package/dist/ui/elements/line-chart.js +2 -2
- package/dist/ui/elements/line-chart.tsx +44 -0
- package/dist/ui/elements/line-chart.works.ts +41 -0
- package/dist/ui/elements/link.js +1 -1
- package/dist/ui/elements/link.tsx +37 -0
- package/dist/ui/elements/link.works.ts +25 -0
- package/dist/ui/elements/media.js +1 -1
- package/dist/ui/elements/media.tsx +56 -0
- package/dist/ui/elements/menu.js +2 -2
- package/dist/ui/elements/menu.tsx +50 -0
- package/dist/ui/elements/menu.works.tsx +153 -0
- package/dist/ui/elements/money.js +1 -1
- package/dist/ui/elements/money.tsx +28 -0
- package/dist/ui/elements/money.works.ts +9 -0
- package/dist/ui/elements/pagination.js +2 -2
- package/dist/ui/elements/pagination.tsx +38 -0
- package/dist/ui/elements/pagination.works.ts +20 -0
- package/dist/ui/elements/progress.js +1 -1
- package/dist/ui/elements/progress.tsx +33 -0
- package/dist/ui/elements/progress.works.ts +8 -0
- package/dist/ui/elements/radio-group.js +2 -2
- package/dist/ui/elements/radio-group.tsx +49 -0
- package/dist/ui/elements/radio-group.works.ts +33 -0
- package/dist/ui/elements/row.js +1 -1
- package/dist/ui/elements/row.tsx +28 -0
- package/dist/ui/elements/scatter.js +1 -1
- package/dist/ui/elements/scatter.tsx +41 -0
- package/dist/ui/elements/scatter.works.ts +38 -0
- package/dist/ui/elements/select.js +2 -2
- package/dist/ui/elements/select.tsx +42 -0
- package/dist/ui/elements/select.works.ts +5 -0
- package/dist/ui/elements/separator.js +1 -1
- package/dist/ui/elements/separator.tsx +30 -0
- package/dist/ui/elements/skeleton.js +1 -1
- package/dist/ui/elements/skeleton.tsx +30 -0
- package/dist/ui/elements/slider.js +1 -1
- package/dist/ui/elements/slider.tsx +33 -0
- package/dist/ui/elements/slider.works.ts +5 -0
- package/dist/ui/elements/sparkline.js +1 -1
- package/dist/ui/elements/sparkline.tsx +36 -0
- package/dist/ui/elements/sparkline.works.ts +32 -0
- package/dist/ui/elements/spinner.js +1 -1
- package/dist/ui/elements/spinner.tsx +32 -0
- package/dist/ui/elements/stack.js +1 -1
- package/dist/ui/elements/stack.tsx +28 -0
- package/dist/ui/elements/stat.js +1 -1
- package/dist/ui/elements/stat.tsx +35 -0
- package/dist/ui/elements/stat.works.ts +14 -0
- package/dist/ui/elements/surface.js +1 -1
- package/dist/ui/elements/surface.tsx +35 -0
- package/dist/ui/elements/surface.works.ts +9 -0
- package/dist/ui/elements/table.js +2 -2
- package/dist/ui/elements/table.tsx +61 -0
- package/dist/ui/elements/table.works.ts +30 -0
- package/dist/ui/elements/tabs.js +2 -2
- package/dist/ui/elements/tabs.tsx +47 -0
- package/dist/ui/elements/tabs.works.ts +84 -0
- package/dist/ui/elements/text.js +1 -1
- package/dist/ui/elements/text.tsx +32 -0
- package/dist/ui/elements/textarea.js +1 -1
- package/dist/ui/elements/textarea.tsx +37 -0
- package/dist/ui/elements/textarea.works.ts +5 -0
- package/dist/ui/elements/time.js +1 -1
- package/dist/ui/elements/time.tsx +34 -0
- package/dist/ui/elements/time.works.ts +25 -0
- package/dist/ui/elements/toggle.js +1 -1
- package/dist/ui/elements/toggle.tsx +33 -0
- package/dist/ui/elements/toggle.works.ts +5 -0
- package/dist/ui/elements/tooltip.js +1 -1
- package/dist/ui/elements/tooltip.tsx +39 -0
- package/dist/ui/elements/tooltip.works.ts +77 -0
- package/dist/ui/elements/value.js +1 -1
- package/dist/ui/elements/value.tsx +30 -0
- package/dist/ui/elements/value.works.ts +9 -0
- package/dist/ui/elements.ts +127 -0
- package/dist/ui/focus-scope.ts +121 -0
- package/dist/ui/fonts.ts +355 -0
- package/dist/ui/generators/dev.ts +79 -0
- package/dist/ui/generators/elements.d.ts +1 -11
- package/dist/ui/generators/elements.js +43 -13
- package/dist/ui/generators/elements.ts +794 -0
- package/dist/ui/generators/i18n.ts +301 -0
- package/dist/ui/generators/icons.ts +214 -0
- package/dist/ui/generators/react.js +10 -3
- package/dist/ui/generators/react.ts +268 -0
- package/dist/ui/generators/routes.d.ts +1 -0
- package/dist/ui/generators/routes.js +89 -69
- package/dist/ui/generators/routes.ts +739 -0
- package/dist/ui/generators/show.ts +259 -0
- package/dist/ui/generators/styles.js +1 -1
- package/dist/ui/generators/styles.ts +670 -0
- package/dist/ui/generators/variants.ts +205 -0
- package/dist/ui/i18n/catalogs/en.ts +191 -0
- package/dist/ui/i18n/format.ts +221 -0
- package/dist/ui/i18n/locales.ts +33 -0
- package/dist/ui/i18n/parse.ts +315 -0
- package/dist/ui/i18n/translator.ts +31 -0
- package/dist/ui/i18n/types.ts +96 -0
- package/dist/ui/icons.ts +331 -0
- package/dist/ui/islands/map.ts +58 -0
- package/dist/ui/localizations.ts +75 -0
- package/dist/ui/makers/message.ts +292 -0
- package/dist/ui/material.ts +236 -0
- package/dist/ui/materials/mat_bordered.ts +13 -0
- package/dist/ui/materials/mat_ceramic.ts +13 -0
- package/dist/ui/materials/mat_clay.ts +13 -0
- package/dist/ui/materials/mat_elevated.ts +24 -0
- package/dist/ui/materials/mat_flat.ts +34 -0
- package/dist/ui/materials/mat_glass.ts +20 -0
- package/dist/ui/materials/mat_lit.ts +13 -0
- package/dist/ui/materials/mat_moulded.ts +11 -0
- package/dist/ui/materials.ts +206 -0
- package/dist/ui/modes.ts +44 -0
- package/dist/ui/navigation.tsx +32 -0
- package/dist/ui/positioning.ts +90 -0
- package/dist/ui/presented.tsx +119 -0
- package/dist/ui/provider.d.ts +2 -0
- package/dist/ui/provider.js +7 -1
- package/dist/ui/provider.tsx +182 -0
- package/dist/ui/render-loop.ts +133 -0
- package/dist/ui/roving-focus.ts +161 -0
- package/dist/ui/screens.js +17 -8
- package/dist/ui/screens.ts +142 -0
- package/dist/ui/scroll-lock.ts +28 -0
- package/dist/ui/series.ts +97 -0
- package/dist/ui/signals.ts +386 -0
- package/dist/ui/styleguide.ts +231 -0
- package/dist/ui/styleguides.ts +374 -0
- package/dist/ui/themes.ts +22 -0
- package/dist/ui/token.js +1 -1
- package/dist/ui/token.ts +78 -0
- package/dist/ui/tokens/shadow.ts +25 -0
- package/dist/ui/tokens.ts +235 -0
- package/dist/ui/validators.ts +232 -0
- package/dist/ui/variant.d.ts +1 -0
- package/dist/ui/variant.js +3 -0
- package/dist/ui/variant.ts +352 -0
- package/dist/ui/variants/var_avatar-round.ts +14 -0
- package/dist/ui/variants/var_bubble-me.ts +14 -0
- package/dist/ui/variants/var_bubble-them.ts +13 -0
- package/dist/ui/variants/var_button-border.ts +76 -0
- package/dist/ui/variants/var_button-cta.ts +76 -0
- package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
- package/dist/ui/variants/var_button-destructive.ts +76 -0
- package/dist/ui/variants/var_button-fill.ts +76 -0
- package/dist/ui/variants/var_button-ghost.ts +72 -0
- package/dist/ui/variants/var_button-glass.ts +55 -0
- package/dist/ui/variants/var_button-normal.ts +76 -0
- package/dist/ui/variants/var_button-quiet.ts +74 -0
- package/dist/ui/variants/var_card.ts +17 -0
- package/dist/ui/variants/var_chip.ts +13 -0
- package/dist/ui/variants/var_choice.ts +15 -0
- package/dist/ui/variants/var_framed.ts +10 -0
- package/dist/ui/variants/var_glyph-plain.ts +11 -0
- package/dist/ui/variants/var_input.ts +43 -0
- package/dist/ui/variants/var_link-inline.ts +29 -0
- package/dist/ui/variants/var_list-row.ts +34 -0
- package/dist/ui/variants/var_rule.ts +10 -0
- package/dist/ui/variants/var_skeleton-block.ts +12 -0
- package/dist/ui/variants/var_spinner-turning.ts +12 -0
- package/dist/ui/variants/var_stat-tile.ts +13 -0
- package/dist/ui/variants/var_tab-bar.ts +15 -0
- package/dist/ui/variants/var_tap-target.ts +25 -0
- package/dist/ui/variants.ts +64 -0
- package/dist/ui/wrappers.d.ts +4 -1
- package/dist/ui/wrappers.js +60 -20
- package/dist/ui/wrappers.tsx +426 -0
- package/package.json +8 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { formatValue } from './validators.ts'
|
|
4
|
+
import { seriesColour, seriesRamp } from './series.ts'
|
|
5
|
+
import { classOfElement } from './variant.ts'
|
|
6
|
+
|
|
7
|
+
import { element, useSurface, useUi } from './wrappers.tsx'
|
|
8
|
+
import type { MessageKey } from './content.tsx'
|
|
9
|
+
|
|
10
|
+
export type Row = Readonly<Record<string, unknown>>
|
|
11
|
+
|
|
12
|
+
export type WhenTight = 'scroll' | 'bucket' | 'wrap' | 'shrink'
|
|
13
|
+
|
|
14
|
+
export interface ChartBase {
|
|
15
|
+
readonly rows: readonly Row[]
|
|
16
|
+
readonly titleKey?: MessageKey
|
|
17
|
+
|
|
18
|
+
readonly whenTight?: WhenTight
|
|
19
|
+
|
|
20
|
+
readonly format?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const num = (row: Row, key: string): number => {
|
|
24
|
+
const v = row[key]
|
|
25
|
+
return typeof v === 'number' ? v : Number(v ?? 0)
|
|
26
|
+
}
|
|
27
|
+
export const str = (row: Row, key: string): string => String(row[key] ?? '')
|
|
28
|
+
|
|
29
|
+
export function useRamp(count: number): readonly string[] {
|
|
30
|
+
const { palette } = useUi()
|
|
31
|
+
return seriesRamp(palette.accent, palette.surface, Math.max(1, count))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function placed(element: string, place: string): string {
|
|
35
|
+
return `${classOfElement(element)}-${place}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function measured(held: Readonly<Record<string, string | number>>): Record<string, string | number> {
|
|
39
|
+
const out: Record<string, string | number> = {}
|
|
40
|
+
for (const [name, value] of Object.entries(held)) out[`--${name}`] = value
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function EmptyFrame({ className }: { className?: string }): React.ReactElement {
|
|
45
|
+
return <div className={className} data-empty="true" />
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SeriesProps extends ChartBase {
|
|
49
|
+
readonly value: string
|
|
50
|
+
readonly at: string
|
|
51
|
+
|
|
52
|
+
readonly by?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function polyline(values: readonly number[], width: number, height: number): string {
|
|
56
|
+
if (values.length === 0) return ''
|
|
57
|
+
const max = Math.max(...values)
|
|
58
|
+
const min = Math.min(...values, 0)
|
|
59
|
+
const span = max - min || 1
|
|
60
|
+
const step = values.length === 1 ? 0 : width / (values.length - 1)
|
|
61
|
+
return values.map((v, i) => `${(i * step).toFixed(2)},${(height - ((v - min) / span) * height).toFixed(2)}`).join(' ')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CategoryProps extends ChartBase {
|
|
65
|
+
readonly value: string
|
|
66
|
+
readonly by: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TwoAxisProps extends ChartBase {
|
|
70
|
+
readonly x: string
|
|
71
|
+
readonly y: string
|
|
72
|
+
readonly value?: string
|
|
73
|
+
}
|
|
74
|
+
|
package/dist/ui/checked.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ELEMENTS } from './elements.ts';
|
|
2
|
-
import { VARIANTS } from './variants.ts';
|
|
1
|
+
import type { ELEMENTS } from './elements.ts';
|
|
2
|
+
import type { VARIANTS } from './variants.ts';
|
|
3
3
|
type Shipped = (typeof VARIANTS)['declares'][number];
|
|
4
4
|
export interface Wearing {
|
|
5
5
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ELEMENTS } from './elements.ts'
|
|
2
|
+
import type { VARIANTS } from './variants.ts'
|
|
3
|
+
|
|
4
|
+
type Shipped = (typeof VARIANTS)['declares'][number]
|
|
5
|
+
|
|
6
|
+
export interface Wearing {}
|
|
7
|
+
|
|
8
|
+
export interface Dispatching {}
|
|
9
|
+
|
|
10
|
+
export type VariantsFor<E extends string> = Shipped extends infer One
|
|
11
|
+
? One extends { readonly id: string; readonly wears: readonly string[] }
|
|
12
|
+
? E extends One['wears'][number]
|
|
13
|
+
? Camel<One['id']>
|
|
14
|
+
: never
|
|
15
|
+
: never
|
|
16
|
+
: never
|
|
17
|
+
|
|
18
|
+
type Pascal<S extends string> = S extends `${infer Head}-${infer Rest}`
|
|
19
|
+
? `${Capitalize<Head>}${Pascal<Rest>}`
|
|
20
|
+
: Capitalize<S>
|
|
21
|
+
|
|
22
|
+
type Camel<S extends string> = S extends `${infer Head}-${infer Rest}` ? `${Head}${Pascal<Rest>}` : S
|
|
23
|
+
|
|
24
|
+
export type ArrangementElement = (typeof ELEMENTS)['declares'][number] extends infer One
|
|
25
|
+
? One extends { readonly arranges: true; readonly id: infer Id extends string }
|
|
26
|
+
? Pascal<Id>
|
|
27
|
+
: never
|
|
28
|
+
: never
|
|
29
|
+
|
|
30
|
+
export function idOf(name: string): string {
|
|
31
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
32
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export interface ClientEvent {
|
|
4
|
+
|
|
5
|
+
readonly property: 'interaction' | 'visit' | 'consent'
|
|
6
|
+
|
|
7
|
+
readonly screen?: string
|
|
8
|
+
|
|
9
|
+
readonly element?: string
|
|
10
|
+
|
|
11
|
+
readonly name?: string
|
|
12
|
+
|
|
13
|
+
readonly action?: string
|
|
14
|
+
|
|
15
|
+
readonly refusedBy?: string
|
|
16
|
+
|
|
17
|
+
readonly attempt?: number
|
|
18
|
+
|
|
19
|
+
readonly at: number
|
|
20
|
+
|
|
21
|
+
readonly seq: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CollectorOptions {
|
|
25
|
+
|
|
26
|
+
readonly endpoint: string
|
|
27
|
+
|
|
28
|
+
readonly allowed: () => boolean
|
|
29
|
+
|
|
30
|
+
readonly sample?: number
|
|
31
|
+
|
|
32
|
+
readonly maxBatch?: number
|
|
33
|
+
|
|
34
|
+
readonly intervalMs?: number
|
|
35
|
+
|
|
36
|
+
readonly now?: () => number
|
|
37
|
+
readonly random?: () => number
|
|
38
|
+
readonly fetch?: typeof globalThis.fetch
|
|
39
|
+
|
|
40
|
+
readonly beacon?: (url: string, body: string) => boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type RecordedEvent = Omit<ClientEvent, 'at' | 'seq'>
|
|
44
|
+
|
|
45
|
+
export interface Collector {
|
|
46
|
+
record: (event: RecordedEvent) => void
|
|
47
|
+
flush: () => Promise<void>
|
|
48
|
+
|
|
49
|
+
stop: () => void
|
|
50
|
+
|
|
51
|
+
dropped: () => number
|
|
52
|
+
|
|
53
|
+
pending: () => number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const INERT_COLLECTOR: Collector = {
|
|
57
|
+
record: () => undefined,
|
|
58
|
+
flush: async () => undefined,
|
|
59
|
+
stop: () => undefined,
|
|
60
|
+
dropped: () => 0,
|
|
61
|
+
pending: () => 0,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createCollector(options: CollectorOptions): Collector {
|
|
65
|
+
const now = options.now ?? (() => Date.now())
|
|
66
|
+
const random = options.random ?? Math.random
|
|
67
|
+
const maxBatch = options.maxBatch ?? 25
|
|
68
|
+
const intervalMs = options.intervalMs ?? 10_000
|
|
69
|
+
|
|
70
|
+
const sampled = (options.sample ?? 1) >= 1 || random() < (options.sample ?? 1)
|
|
71
|
+
if (!sampled) return INERT_COLLECTOR
|
|
72
|
+
|
|
73
|
+
let buffer: ClientEvent[] = []
|
|
74
|
+
let dropped = 0
|
|
75
|
+
let stopped = false
|
|
76
|
+
|
|
77
|
+
let seq = 0
|
|
78
|
+
|
|
79
|
+
const send = async (batch: readonly ClientEvent[]): Promise<void> => {
|
|
80
|
+
if (batch.length === 0) return
|
|
81
|
+
const body = JSON.stringify({ events: batch })
|
|
82
|
+
|
|
83
|
+
if (options.beacon !== undefined && options.beacon(options.endpoint, body)) return
|
|
84
|
+
|
|
85
|
+
const doFetch = options.fetch ?? globalThis.fetch
|
|
86
|
+
if (doFetch === undefined) {
|
|
87
|
+
dropped += batch.length
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const response = await doFetch(options.endpoint, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: { 'content-type': 'application/json' },
|
|
95
|
+
|
|
96
|
+
keepalive: true,
|
|
97
|
+
body,
|
|
98
|
+
})
|
|
99
|
+
if (!response.ok) dropped += batch.length
|
|
100
|
+
} catch {
|
|
101
|
+
|
|
102
|
+
dropped += batch.length
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const flush = async (): Promise<void> => {
|
|
107
|
+
const batch = buffer
|
|
108
|
+
buffer = []
|
|
109
|
+
await send(batch)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const record = (event: RecordedEvent): void => {
|
|
113
|
+
|
|
114
|
+
if (stopped || !options.allowed()) return
|
|
115
|
+
|
|
116
|
+
buffer.push({ ...event, at: now(), seq: ++seq })
|
|
117
|
+
if (buffer.length >= maxBatch) void flush()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const timer = setInterval(() => void flush(), intervalMs)
|
|
121
|
+
|
|
122
|
+
;(timer as unknown as { unref?: () => void }).unref?.()
|
|
123
|
+
|
|
124
|
+
const onHide = (): void => {
|
|
125
|
+
if (globalThis.document?.visibilityState === 'hidden') void flush()
|
|
126
|
+
}
|
|
127
|
+
globalThis.document?.addEventListener('visibilitychange', onHide)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
record,
|
|
131
|
+
flush,
|
|
132
|
+
stop: () => {
|
|
133
|
+
stopped = true
|
|
134
|
+
clearInterval(timer)
|
|
135
|
+
globalThis.document?.removeEventListener('visibilitychange', onHide)
|
|
136
|
+
},
|
|
137
|
+
dropped: () => dropped,
|
|
138
|
+
pending: () => buffer.length,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -7,13 +7,22 @@ export declare const COMPOSITIONS: Threaded & {
|
|
|
7
7
|
readonly files: string;
|
|
8
8
|
};
|
|
9
9
|
export declare function nameOf(at: string): string;
|
|
10
|
-
export declare
|
|
10
|
+
export declare const catalogue: () => Promise<readonly string[]>;
|
|
11
|
+
export declare const screening: () => Promise<readonly string[]>;
|
|
11
12
|
export declare function composed(body: string, rows: readonly {
|
|
12
13
|
readonly id: string;
|
|
13
14
|
readonly at?: string;
|
|
14
15
|
}[], from: 'screens' | 'compositions', except?: string): readonly string[];
|
|
16
|
+
export declare const HOME: string;
|
|
15
17
|
export declare const DOORS: ReadonlySet<string>;
|
|
16
18
|
export declare function reaching(at: string, text: string, elements: ReadonlySet<string>, declared: ReadonlySet<string>): readonly string[];
|
|
19
|
+
export declare const COMPILED = "app/_generated/compiled";
|
|
20
|
+
export declare function compiledAt(at: string): string;
|
|
21
|
+
export declare function settled(root: string, rows: readonly Threaded[], checking: boolean, by: string, kept: Set<string>): readonly string[];
|
|
22
|
+
export declare const RUNTIME: readonly string[];
|
|
23
|
+
export declare function emitting(root: string, entries: readonly string[], checking: boolean, kept: Set<string>): readonly string[];
|
|
24
|
+
export declare function stale(root: string, kept: ReadonlySet<string>, checking: boolean): readonly string[];
|
|
25
|
+
export declare function handing(at: string, text: string): readonly string[];
|
|
17
26
|
export declare function undeclared(tree: Tree, rows: readonly Threaded[]): Promise<readonly string[]>;
|
|
18
27
|
export declare function seeds(rows: readonly Threaded[], held: (at: string) => boolean): readonly {
|
|
19
28
|
readonly path: string;
|
package/dist/ui/compositions.js
CHANGED
|
@@ -6,14 +6,15 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
-
import { basename, dirname, join } from 'node:path';
|
|
11
|
-
import { stdout } from 'node:process';
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
11
|
+
import { argv, stdout } from 'node:process';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
13
|
import { wrapped } from "@prestack/thread/core/asking";
|
|
14
14
|
import { exit, invoked } from "@prestack/thread/core/invoked";
|
|
15
15
|
import { named as packageOf } from "@prestack/thread/core/scope";
|
|
16
16
|
import { findTree, inTree } from "@prestack/thread/core/tree";
|
|
17
|
+
import { compiled } from "./generators/react.js";
|
|
17
18
|
export const TEACHES = [
|
|
18
19
|
' what the body may reach, and how a premium state is written:',
|
|
19
20
|
'',
|
|
@@ -21,23 +22,27 @@ export const TEACHES = [
|
|
|
21
22
|
' the row that declared itself the caller — and `translate`, `clock` and `dispatch`. Name',
|
|
22
23
|
' it in the body and the import is written for you.',
|
|
23
24
|
'',
|
|
24
|
-
' A premium
|
|
25
|
-
'
|
|
26
|
-
'
|
|
25
|
+
' A premium look is not decided at the call site. A grant already says who may run an',
|
|
26
|
+
' operation, in words your own entity declares, and a signal that names that operation',
|
|
27
|
+
' holds for exactly the callers the grant refuses. The rule is written once, on the',
|
|
28
|
+
' grant, and every screen follows it:',
|
|
27
29
|
'',
|
|
28
|
-
' <FeatureBox.paywalled
|
|
30
|
+
' <FeatureBox.paywalled>',
|
|
29
31
|
' <Text k=\'feature.title\' />',
|
|
30
32
|
' </FeatureBox.paywalled>',
|
|
31
33
|
'',
|
|
32
34
|
' Read it in three parts, and each is its own command:',
|
|
33
35
|
'',
|
|
34
|
-
' `locked` is a
|
|
35
|
-
' `
|
|
36
|
-
'
|
|
36
|
+
' `locked` is a signal the ELEMENT declares it raises — `thread create element` with',
|
|
37
|
+
' `raises: locked`. A signal this app declares cannot be raised on an element that',
|
|
38
|
+
' never said it raises one, so `Box` will not take it. Nothing passes it here: it is',
|
|
39
|
+
' set for you wherever the gate refuses the operation the signal names.',
|
|
37
40
|
' `.paywalled` is a variant worn as the TAG, never passed as a prop, and it is what',
|
|
38
41
|
' carries the look — `thread create variant` with a `state: locked` and what it sets.',
|
|
39
|
-
'
|
|
40
|
-
'
|
|
42
|
+
' Declare that state on a MATERIAL instead and every variant made of it locks the same',
|
|
43
|
+
' way, which is how one overlay dresses a whole app from one row.',
|
|
44
|
+
' The rule itself is `thread grant`, and the words in it are options on the value your',
|
|
45
|
+
' entity names as saying what a caller may do — `thread create entity`.',
|
|
41
46
|
'',
|
|
42
47
|
' THE MEMBER IS THE VARIANT\'S WHOLE ID, CAMELCASED — never the part after the element',
|
|
43
48
|
' name. A variant `button-fill` is worn `<Button.buttonFill>`, and `<Button.fill>` is not',
|
|
@@ -78,6 +83,22 @@ export const TEACHES = [
|
|
|
78
83
|
' A name no screen has and a destination not written out as a name are each refused there.',
|
|
79
84
|
' Never an `href` and never',
|
|
80
85
|
' a router: a move the map cannot see is a screen the map reads as unreachable.',
|
|
86
|
+
'',
|
|
87
|
+
' how a screen shows data:',
|
|
88
|
+
'',
|
|
89
|
+
' A screen NAMES the reads it binds, and never fetches. `views` takes the ids of declared',
|
|
90
|
+
' views, the framework runs each one, and `useView` hands back what came out:',
|
|
91
|
+
'',
|
|
92
|
+
' export const Orders = screen(\'orders\', { as: \'page\', views: [\'vw_orders\'] }, () => (',
|
|
93
|
+
' <Each rows={useView<readonly Row[]>(\'vw_orders\') ?? []} by="id">',
|
|
94
|
+
' {(row) => <Text k="orders.line" with={{ name: row.name }} />}',
|
|
95
|
+
' </Each>',
|
|
96
|
+
' ))',
|
|
97
|
+
'',
|
|
98
|
+
' `useView` comes through the same door as `useUi`, and it refuses a name the screen did not',
|
|
99
|
+
' bind. `thread routes` refuses a view this app declares no operation for, and writes what each',
|
|
100
|
+
' screen shows into the map. A fetch of your own, or a hook you wrote, is how a screen ends up',
|
|
101
|
+
' showing something nothing declared, and there is no second way in.',
|
|
81
102
|
];
|
|
82
103
|
export const REACHES = /\buseUi\s*\(/;
|
|
83
104
|
const here = 'thread/ui/compositions.ts';
|
|
@@ -129,7 +150,7 @@ async function sheetOf(tree, file, ...named) {
|
|
|
129
150
|
}
|
|
130
151
|
const camelOf = (id) => id.replace(/-([a-z0-9])/g, (_, one) => one.toUpperCase());
|
|
131
152
|
const sentence = (one, pad) => String(one.describes ?? '').trim() === '' ? [] : wrapped(String(one.describes), pad);
|
|
132
|
-
|
|
153
|
+
async function listed(tree, making) {
|
|
133
154
|
const [elements, variants, compositions, screens] = await Promise.all([
|
|
134
155
|
sheetOf(tree, `elements.${'ts'}`, 'ELEMENT', 'ELEMENTS'),
|
|
135
156
|
sheetOf(tree, `variants.${'ts'}`, 'VARIANT', 'VARIANTS'),
|
|
@@ -190,6 +211,8 @@ export async function catalogue(tree, making) {
|
|
|
190
211
|
'',
|
|
191
212
|
];
|
|
192
213
|
}
|
|
214
|
+
export const catalogue = async () => listed(findTree(), 'composition');
|
|
215
|
+
export const screening = async () => listed(findTree(), 'screen');
|
|
193
216
|
const TAG = /<([A-Z][A-Za-z0-9]*)/g;
|
|
194
217
|
export function composed(body, rows, from, except) {
|
|
195
218
|
const named = new Map();
|
|
@@ -210,8 +233,10 @@ export function composed(body, rows, from, except) {
|
|
|
210
233
|
});
|
|
211
234
|
}
|
|
212
235
|
const IMPORTS = /^import\s[\s\S]*?from\s*['"]([^'"]+)['"]/gm;
|
|
213
|
-
|
|
214
|
-
const
|
|
236
|
+
const FRAMEWORK = 'thread/';
|
|
237
|
+
export const HOME = `${packageOf('ui')}/${here.slice(FRAMEWORK.length).split('/').slice(0, -1).join('/')}`;
|
|
238
|
+
export const DOORS = new Set(['react', `${HOME}/wrappers`, `${HOME}/drag`]);
|
|
239
|
+
const SHIPPED = new RegExp(`^${HOME}/elements/([a-z0-9-]+)$`);
|
|
215
240
|
const slashed = (path) => path.split('\\').join('/');
|
|
216
241
|
export function reaching(at, text, elements, declared) {
|
|
217
242
|
const here = slashed(at).split('/').slice(0, -1).join('/');
|
|
@@ -229,6 +254,193 @@ export function reaching(at, text, elements, declared) {
|
|
|
229
254
|
}
|
|
230
255
|
return [...found].sort();
|
|
231
256
|
}
|
|
257
|
+
const CLAUSE = /^import\s+(?!type\s)([\s\S]*?)\s*from\s*['"][^'"]+['"]/gm;
|
|
258
|
+
const WORD = /(?:^|[,{]|\s)(?:[A-Za-z_$][\w$]*\s+as\s+)?([A-Za-z_$][\w$]*)/g;
|
|
259
|
+
const NOT_A_NAME = new Set(['type', 'as', 'from', 'import']);
|
|
260
|
+
const KEPT = 'a composition keeps no state of its own. State belongs to the element that raises the signal, in the works file beside it';
|
|
261
|
+
const RUNS = 'nothing here runs on its own. A read is bound by the screen in its own views list, and a press is answered by dispatch';
|
|
262
|
+
const REFUSED = {
|
|
263
|
+
useState: KEPT,
|
|
264
|
+
useReducer: KEPT,
|
|
265
|
+
useSyncExternalStore: KEPT,
|
|
266
|
+
useEffect: RUNS,
|
|
267
|
+
useLayoutEffect: RUNS,
|
|
268
|
+
element: 'only the compiler writes an element, and a file that calls it turns off the checks that keep this one declared',
|
|
269
|
+
};
|
|
270
|
+
export const COMPILED = 'app/_generated/compiled';
|
|
271
|
+
export function compiledAt(at) {
|
|
272
|
+
return `${COMPILED}/${slashed(at)}`;
|
|
273
|
+
}
|
|
274
|
+
const ENDINGS = ['.tsx', '.ts'];
|
|
275
|
+
function beside(root, where, said) {
|
|
276
|
+
if (!said.startsWith(`${HOME}/`))
|
|
277
|
+
return undefined;
|
|
278
|
+
const tail = said.slice(HOME.length + 1);
|
|
279
|
+
for (const ending of ENDINGS) {
|
|
280
|
+
const at = `${COMPILED}/ui/${tail}${ending}`;
|
|
281
|
+
if (!existsSync(join(root, at)))
|
|
282
|
+
continue;
|
|
283
|
+
const out = slashed(relative(dirname(where), at));
|
|
284
|
+
return out.startsWith('.') ? out : `./${out}`;
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
const NAMED_IMPORT = /(['"])(@[^'"]+)\1/g;
|
|
289
|
+
export function settled(root, rows, checking, by, kept) {
|
|
290
|
+
const said = [];
|
|
291
|
+
for (const row of rows) {
|
|
292
|
+
const file = join(root, row.at);
|
|
293
|
+
if (!existsSync(file))
|
|
294
|
+
continue;
|
|
295
|
+
const held = compiled(readFileSync(file, 'utf8'), row.at);
|
|
296
|
+
if (held.because !== undefined) {
|
|
297
|
+
said.push(held.because);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const where = compiledAt(row.at);
|
|
301
|
+
kept.add(where);
|
|
302
|
+
const text = (held.text ?? '').replace(NAMED_IMPORT, (whole, quote, said) => {
|
|
303
|
+
const found = beside(root, where, said);
|
|
304
|
+
return found === undefined ? whole : `${quote}${found}${quote}`;
|
|
305
|
+
});
|
|
306
|
+
const at = join(root, where);
|
|
307
|
+
if (!checking) {
|
|
308
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
309
|
+
writeFileSync(at, text, 'utf8');
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (existsSync(at) && readFileSync(at, 'utf8') === text)
|
|
313
|
+
continue;
|
|
314
|
+
said.push(`${where} is not what the compiler writes from ${row.at}. A compiled file that differs from its ` +
|
|
315
|
+
`source is a hand edit wearing a generated name. Run \`${by}\` and commit what it writes.`);
|
|
316
|
+
}
|
|
317
|
+
return said;
|
|
318
|
+
}
|
|
319
|
+
const ORG = packageOf('ui').split('/')[0] ?? '';
|
|
320
|
+
const REACHED = /^(?:import|export)\s+(type\s+)?(?:(?:[^\n]|\n(?=[ \t}]))*?\s)?from\s*['"]([^'"]+)['"]/gm;
|
|
321
|
+
export const RUNTIME = ['wrappers.tsx', 'drag.ts', 'provider.tsx', 'islands/map.ts'];
|
|
322
|
+
const NODE = /^node:/;
|
|
323
|
+
function installs(root) {
|
|
324
|
+
try {
|
|
325
|
+
const held = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
326
|
+
return new Set([...Object.keys(held.dependencies ?? {}), ...Object.keys(held.devDependencies ?? {})]);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return new Set();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function packaging(said) {
|
|
333
|
+
return said.startsWith('@') ? said.split('/').slice(0, 2).join('/') : (said.split('/')[0] ?? said);
|
|
334
|
+
}
|
|
335
|
+
export function emitting(root, entries, checking, kept) {
|
|
336
|
+
const has = installs(root);
|
|
337
|
+
const from = join(root, 'node_modules', packageOf('ui'), 'dist', 'ui');
|
|
338
|
+
const said = [];
|
|
339
|
+
if (!existsSync(from)) {
|
|
340
|
+
return [
|
|
341
|
+
`this app declares an interface and ${packageOf('ui')} is not installed, so there is nothing to emit it from. ` +
|
|
342
|
+
`Install it and run \`thread attach ${packageOf('ui')}\` again.`,
|
|
343
|
+
];
|
|
344
|
+
}
|
|
345
|
+
const seen = new Set();
|
|
346
|
+
const walk = (at) => {
|
|
347
|
+
if (seen.has(at))
|
|
348
|
+
return;
|
|
349
|
+
seen.add(at);
|
|
350
|
+
const file = join(from, at);
|
|
351
|
+
if (!existsSync(file)) {
|
|
352
|
+
said.push(`${at} is part of the interface this app emits, and the installed ${packageOf('ui')} does not hold it. ` +
|
|
353
|
+
'Upgrade the framework packages and run this again.');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const text = readFileSync(file, 'utf8');
|
|
357
|
+
const where = `${COMPILED}/ui/${at}`;
|
|
358
|
+
kept.add(where);
|
|
359
|
+
const to = join(root, where);
|
|
360
|
+
if (checking) {
|
|
361
|
+
if (!existsSync(to) || readFileSync(to, 'utf8') !== text) {
|
|
362
|
+
said.push(`${where} is not what this app emits from ${packageOf('ui')}. An emitted file that differs from ` +
|
|
363
|
+
'its source is a hand edit wearing a generated name. Run `thread compositions` and commit what it writes.');
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
368
|
+
writeFileSync(to, text, 'utf8');
|
|
369
|
+
}
|
|
370
|
+
for (const one of text.matchAll(REACHED)) {
|
|
371
|
+
const named = one[2] ?? '';
|
|
372
|
+
if (named.startsWith('.')) {
|
|
373
|
+
const next = slashed(join(dirname(at), named));
|
|
374
|
+
if (next.startsWith('..')) {
|
|
375
|
+
said.push(`${at} reaches \`${named}\`, which is outside the interface this app emits.`);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
walk(next);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (one[1] !== undefined)
|
|
382
|
+
continue;
|
|
383
|
+
if (!named.startsWith(`${ORG}/`)) {
|
|
384
|
+
const held = packaging(named);
|
|
385
|
+
if (NODE.test(named) || has.has(held))
|
|
386
|
+
continue;
|
|
387
|
+
said.push(`${at} reaches ${held}, and this app installs nothing by that name. An emitted interface brings ` +
|
|
388
|
+
`what it reaches with it, so ${held} has to arrive when ` +
|
|
389
|
+
`thread attach ${packageOf('ui')} runs.`);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
said.push(`${at} reaches \`${named}\`, so emitting it would leave this app importing the framework while it runs. ` +
|
|
393
|
+
'It reaches what is emitted beside it, or nothing of this framework at all.');
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
for (const one of entries)
|
|
397
|
+
walk(one);
|
|
398
|
+
return said;
|
|
399
|
+
}
|
|
400
|
+
export function stale(root, kept, checking) {
|
|
401
|
+
const said = [];
|
|
402
|
+
const walk = (at) => {
|
|
403
|
+
const where = join(root, at);
|
|
404
|
+
if (!existsSync(where))
|
|
405
|
+
return;
|
|
406
|
+
for (const each of readdirSync(where, { withFileTypes: true })) {
|
|
407
|
+
const path = `${at}/${each.name}`;
|
|
408
|
+
if (each.isDirectory()) {
|
|
409
|
+
walk(path);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (kept.has(path))
|
|
413
|
+
continue;
|
|
414
|
+
if (checking) {
|
|
415
|
+
said.push(`${path} sits under ${COMPILED} and nothing declares what it was written from any more. ` +
|
|
416
|
+
'Run `thread compositions` and it goes.');
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
rmSync(join(root, path), { force: true });
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
walk(COMPILED);
|
|
423
|
+
return said;
|
|
424
|
+
}
|
|
425
|
+
export function handing(at, text) {
|
|
426
|
+
const found = [];
|
|
427
|
+
for (const one of text.matchAll(CLAUSE)) {
|
|
428
|
+
for (const name of (one[1] ?? '').matchAll(WORD)) {
|
|
429
|
+
const held = name[1];
|
|
430
|
+
if (held === undefined || NOT_A_NAME.has(held))
|
|
431
|
+
continue;
|
|
432
|
+
const why = REFUSED[held];
|
|
433
|
+
if (why === undefined)
|
|
434
|
+
continue;
|
|
435
|
+
found.push(`${at} imports \`${held}\`, and ${why}.`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (/\bfetch\s*\(/.test(text)) {
|
|
439
|
+
found.push(`${at} calls \`fetch\`, and nothing here reaches the network by hand. A screen names the reads it ` +
|
|
440
|
+
'binds in its own views list, and the framework runs each one.');
|
|
441
|
+
}
|
|
442
|
+
return found;
|
|
443
|
+
}
|
|
232
444
|
export async function undeclared(tree, rows) {
|
|
233
445
|
const elements = new Set();
|
|
234
446
|
const declared = new Set();
|
|
@@ -253,7 +465,9 @@ export async function undeclared(tree, rows) {
|
|
|
253
465
|
const file = join(tree.at, row.at);
|
|
254
466
|
if (!existsSync(file))
|
|
255
467
|
continue;
|
|
256
|
-
|
|
468
|
+
const text = readFileSync(file, 'utf8');
|
|
469
|
+
found.push(...handing(row.at, text));
|
|
470
|
+
for (const one of reaching(row.at, text, elements, declared)) {
|
|
257
471
|
found.push(`${row.at} imports \`${one}\`, which this app declares nothing by. A composition reaches ` +
|
|
258
472
|
'declared elements, other declared compositions, and the door — everything else is a thing ' +
|
|
259
473
|
'nothing wrote down. Declare it, or take it out and write the body through `thread create`.');
|
|
@@ -265,8 +479,8 @@ function starter(row) {
|
|
|
265
479
|
const name = nameOf(row.at);
|
|
266
480
|
return `'use client'
|
|
267
481
|
|
|
268
|
-
import { Box } from '
|
|
269
|
-
import { composition } from '
|
|
482
|
+
import { Box } from '${HOME}/elements/box'
|
|
483
|
+
import { composition } from '${HOME}/wrappers'
|
|
270
484
|
|
|
271
485
|
export const ${name} = composition('${name}', () => <Box.card />)
|
|
272
486
|
`;
|
|
@@ -287,13 +501,38 @@ async function run() {
|
|
|
287
501
|
return 1;
|
|
288
502
|
}
|
|
289
503
|
const rows = held.COMPOSITION.declares ?? [];
|
|
504
|
+
const checking = argv.includes('--check');
|
|
290
505
|
const made = seeds(rows, (where) => existsSync(join(tree.at, where)));
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
506
|
+
const unseeded = made[0];
|
|
507
|
+
if (checking && unseeded !== undefined) {
|
|
508
|
+
stdout.write(`thread: ${unseeded.path} is declared and the file is not there yet. A row names a file, and ` +
|
|
509
|
+
'`--check` never writes one — run `thread compositions` and commit what it seeds.\n');
|
|
510
|
+
return 1;
|
|
511
|
+
}
|
|
512
|
+
if (!checking) {
|
|
513
|
+
for (const one of made) {
|
|
514
|
+
mkdirSync(dirname(join(tree.at, one.path)), { recursive: true });
|
|
515
|
+
writeFileSync(join(tree.at, one.path), one.content, 'utf8');
|
|
516
|
+
stdout.write(`thread: seeded ${one.path}\n`);
|
|
517
|
+
}
|
|
295
518
|
}
|
|
296
|
-
const
|
|
519
|
+
const parts = await sheetOf(tree, 'elements.ts', 'ELEMENT', 'ELEMENTS');
|
|
520
|
+
const shipped = `node_modules/${packageOf('ui')}/dist/ui/`;
|
|
521
|
+
const entries = [
|
|
522
|
+
...RUNTIME,
|
|
523
|
+
...parts.flatMap((one) => (one.at ?? '').startsWith(shipped) ? [(one.at ?? '').slice(shipped.length).replace(/\.js$/, '.tsx')] : []),
|
|
524
|
+
];
|
|
525
|
+
const kept = new Set();
|
|
526
|
+
for (const one of await sheetOf(tree, 'screens.ts', 'SCREEN', 'SCREENS')) {
|
|
527
|
+
if (one.at !== undefined && one.at !== '')
|
|
528
|
+
kept.add(compiledAt(one.at));
|
|
529
|
+
}
|
|
530
|
+
const found = [
|
|
531
|
+
...(await undeclared(tree, rows)),
|
|
532
|
+
...emitting(tree.at, entries, checking, kept),
|
|
533
|
+
...settled(tree.at, rows, checking, 'thread compositions', kept),
|
|
534
|
+
];
|
|
535
|
+
const loose = [...found, ...(found.length === 0 ? stale(tree.at, kept, checking) : [])];
|
|
297
536
|
for (const one of loose)
|
|
298
537
|
stdout.write(`thread: ${one}\n`);
|
|
299
538
|
stdout.write(`thread: ${String(rows.length)} composition(s) declared, ${String(made.length)} seeded, ` +
|