@prestack/thread-ui 0.1.1 → 0.1.3
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/CHANGELOG.md +518 -0
- package/dist/ui/animations/anm_appear.d.ts +8 -8
- package/dist/ui/animations/anm_blur_in.d.ts +12 -12
- package/dist/ui/animations/anm_blur_out.d.ts +12 -12
- package/dist/ui/animations/anm_dismiss.d.ts +12 -12
- package/dist/ui/animations/anm_drop.d.ts +12 -12
- package/dist/ui/animations/anm_fall.d.ts +12 -12
- package/dist/ui/animations/anm_flash.d.ts +9 -9
- package/dist/ui/animations/anm_glow.d.ts +8 -8
- package/dist/ui/animations/anm_lift.d.ts +8 -8
- package/dist/ui/animations/anm_nudge.d.ts +8 -8
- package/dist/ui/animations/anm_pop_in.d.ts +16 -16
- package/dist/ui/animations/anm_pop_out.d.ts +16 -16
- package/dist/ui/animations/anm_press.d.ts +9 -9
- package/dist/ui/animations/anm_pulse.d.ts +10 -10
- package/dist/ui/animations/anm_rise.d.ts +12 -12
- package/dist/ui/animations/anm_shake.d.ts +11 -11
- package/dist/ui/animations/anm_slide_in_left.d.ts +12 -12
- package/dist/ui/animations/anm_slide_in_right.d.ts +12 -12
- package/dist/ui/animations/anm_slide_out_left.d.ts +12 -12
- package/dist/ui/animations/anm_slide_out_right.d.ts +12 -12
- package/dist/ui/animations/anm_spin.d.ts +8 -8
- package/dist/ui/animations/anm_swell.d.ts +8 -8
- package/dist/ui/animations/anm_wobble.d.ts +13 -13
- package/dist/ui/animations/anm_zoom_in.d.ts +12 -12
- package/dist/ui/animations/anm_zoom_out.d.ts +12 -12
- package/dist/ui/animations.d.ts +8 -8
- package/dist/ui/animations.js +4 -4
- package/dist/ui/animations.ts +1 -1
- package/dist/ui/attach.js +25 -80
- package/dist/ui/attach.ts +12 -69
- package/dist/ui/charts.d.ts +0 -3
- package/dist/ui/charts.js +3 -7
- package/dist/ui/charts.tsx +0 -4
- package/dist/ui/checked.d.ts +1 -1
- package/dist/ui/checked.ts +1 -1
- package/dist/ui/collector.js +1 -1
- package/dist/ui/collector.ts +1 -1
- package/dist/ui/compositions.d.ts +9 -3
- package/dist/ui/compositions.js +9 -158
- package/dist/ui/compositions.ts +6 -170
- package/dist/ui/content.js +4 -7
- package/dist/ui/content.tsx +4 -15
- package/dist/ui/controls.js +1 -1
- package/dist/ui/door.d.ts +1 -1
- package/dist/ui/elements/accordion.d.ts +2 -1
- package/dist/ui/elements/accordion.js +4 -4
- package/dist/ui/elements/accordion.tsx +2 -1
- package/dist/ui/elements/aspect-ratio.d.ts +2 -1
- package/dist/ui/elements/aspect-ratio.js +1 -1
- package/dist/ui/elements/aspect-ratio.tsx +2 -1
- package/dist/ui/elements/avatar.d.ts +2 -1
- package/dist/ui/elements/avatar.js +3 -3
- package/dist/ui/elements/avatar.tsx +2 -1
- package/dist/ui/elements/badge.d.ts +2 -1
- package/dist/ui/elements/badge.js +3 -3
- package/dist/ui/elements/badge.tsx +2 -1
- package/dist/ui/elements/bars.d.ts +2 -1
- package/dist/ui/elements/bars.js +5 -5
- package/dist/ui/elements/bars.tsx +2 -1
- package/dist/ui/elements/bars.works.js +3 -3
- package/dist/ui/elements/box.d.ts +2 -1
- package/dist/ui/elements/box.js +1 -1
- package/dist/ui/elements/box.tsx +2 -1
- package/dist/ui/elements/breadcrumb.d.ts +2 -1
- package/dist/ui/elements/breadcrumb.js +5 -5
- package/dist/ui/elements/breadcrumb.tsx +2 -1
- package/dist/ui/elements/breadcrumb.works.js +1 -1
- package/dist/ui/elements/button.d.ts +2 -1
- package/dist/ui/elements/button.js +3 -3
- package/dist/ui/elements/button.tsx +2 -1
- package/dist/ui/elements/button.works.js +2 -2
- package/dist/ui/elements/column.d.ts +2 -1
- package/dist/ui/elements/column.js +1 -1
- package/dist/ui/elements/column.tsx +2 -1
- package/dist/ui/elements/consent.d.ts +2 -1
- package/dist/ui/elements/consent.js +10 -10
- package/dist/ui/elements/consent.tsx +2 -1
- package/dist/ui/elements/consent.works.js +1 -1
- package/dist/ui/elements/donut.d.ts +2 -1
- package/dist/ui/elements/donut.js +4 -4
- package/dist/ui/elements/donut.tsx +2 -1
- package/dist/ui/elements/donut.works.js +2 -2
- package/dist/ui/elements/each.d.ts +2 -1
- package/dist/ui/elements/each.js +3 -3
- package/dist/ui/elements/each.tsx +2 -1
- package/dist/ui/elements/each.works.js +2 -2
- package/dist/ui/elements/empty.d.ts +2 -1
- package/dist/ui/elements/empty.js +2 -2
- package/dist/ui/elements/empty.tsx +2 -1
- package/dist/ui/elements/field.d.ts +2 -1
- package/dist/ui/elements/field.js +3 -3
- package/dist/ui/elements/field.tsx +2 -1
- package/dist/ui/elements/field.works.js +1 -1
- package/dist/ui/elements/funnel.d.ts +2 -1
- package/dist/ui/elements/funnel.js +5 -5
- package/dist/ui/elements/funnel.tsx +2 -1
- package/dist/ui/elements/funnel.works.js +3 -3
- package/dist/ui/elements/gauge.d.ts +2 -1
- package/dist/ui/elements/gauge.js +3 -3
- package/dist/ui/elements/gauge.tsx +2 -1
- package/dist/ui/elements/gauge.works.js +1 -1
- package/dist/ui/elements/glyph.d.ts +2 -1
- package/dist/ui/elements/glyph.js +3 -3
- package/dist/ui/elements/glyph.tsx +2 -1
- package/dist/ui/elements/glyph.works.js +1 -1
- package/dist/ui/elements/grid.d.ts +2 -1
- package/dist/ui/elements/grid.js +1 -1
- package/dist/ui/elements/grid.tsx +2 -1
- package/dist/ui/elements/heatmap.d.ts +2 -1
- package/dist/ui/elements/heatmap.js +5 -5
- package/dist/ui/elements/heatmap.tsx +2 -1
- package/dist/ui/elements/heatmap.works.js +1 -1
- package/dist/ui/elements/hit-box.d.ts +2 -1
- package/dist/ui/elements/hit-box.js +2 -2
- package/dist/ui/elements/hit-box.tsx +2 -1
- package/dist/ui/elements/hit-box.works.d.ts +2 -2
- package/dist/ui/elements/hit-box.works.js +5 -5
- package/dist/ui/elements/hit-box.works.tsx +2 -1
- package/dist/ui/elements/image.d.ts +2 -1
- package/dist/ui/elements/image.js +2 -2
- package/dist/ui/elements/image.tsx +2 -1
- package/dist/ui/elements/line-chart.d.ts +2 -1
- package/dist/ui/elements/line-chart.js +4 -4
- package/dist/ui/elements/line-chart.tsx +2 -1
- package/dist/ui/elements/line-chart.works.js +2 -2
- package/dist/ui/elements/link.d.ts +2 -1
- package/dist/ui/elements/link.js +3 -3
- package/dist/ui/elements/link.tsx +2 -1
- package/dist/ui/elements/link.works.js +1 -1
- package/dist/ui/elements/media.d.ts +2 -1
- package/dist/ui/elements/media.js +2 -2
- package/dist/ui/elements/media.tsx +2 -1
- package/dist/ui/elements/menu.d.ts +2 -1
- package/dist/ui/elements/menu.js +5 -5
- package/dist/ui/elements/menu.tsx +2 -1
- package/dist/ui/elements/menu.works.d.ts +2 -1
- package/dist/ui/elements/menu.works.js +5 -5
- package/dist/ui/elements/menu.works.tsx +2 -1
- package/dist/ui/elements/money.d.ts +3 -1
- package/dist/ui/elements/money.js +10 -5
- package/dist/ui/elements/money.tsx +12 -3
- package/dist/ui/elements/money.works.js +1 -1
- package/dist/ui/elements/pagination.d.ts +2 -1
- package/dist/ui/elements/pagination.js +5 -5
- package/dist/ui/elements/pagination.tsx +2 -1
- package/dist/ui/elements/progress.d.ts +2 -1
- package/dist/ui/elements/progress.js +3 -3
- package/dist/ui/elements/progress.tsx +2 -1
- package/dist/ui/elements/radio-group.d.ts +2 -1
- package/dist/ui/elements/radio-group.js +4 -4
- package/dist/ui/elements/radio-group.tsx +2 -1
- package/dist/ui/elements/radio-group.works.js +1 -1
- package/dist/ui/elements/row.d.ts +2 -1
- package/dist/ui/elements/row.js +1 -1
- package/dist/ui/elements/row.tsx +2 -1
- package/dist/ui/elements/scatter.d.ts +2 -1
- package/dist/ui/elements/scatter.js +3 -3
- package/dist/ui/elements/scatter.tsx +2 -1
- package/dist/ui/elements/scatter.works.js +1 -1
- package/dist/ui/elements/select.d.ts +2 -1
- package/dist/ui/elements/select.js +4 -4
- package/dist/ui/elements/select.tsx +2 -1
- package/dist/ui/elements/separator.d.ts +2 -1
- package/dist/ui/elements/separator.js +2 -2
- package/dist/ui/elements/separator.tsx +2 -1
- package/dist/ui/elements/skeleton.d.ts +2 -1
- package/dist/ui/elements/skeleton.js +2 -2
- package/dist/ui/elements/skeleton.tsx +2 -1
- package/dist/ui/elements/slider.d.ts +2 -1
- package/dist/ui/elements/slider.js +3 -3
- package/dist/ui/elements/slider.tsx +2 -1
- package/dist/ui/elements/sparkline.d.ts +2 -1
- package/dist/ui/elements/sparkline.js +3 -3
- package/dist/ui/elements/sparkline.tsx +2 -1
- package/dist/ui/elements/sparkline.works.js +1 -1
- package/dist/ui/elements/spinner.d.ts +2 -1
- package/dist/ui/elements/spinner.js +2 -2
- package/dist/ui/elements/spinner.tsx +2 -1
- package/dist/ui/elements/stack.d.ts +2 -1
- package/dist/ui/elements/stack.js +1 -1
- package/dist/ui/elements/stack.tsx +2 -1
- package/dist/ui/elements/stat.d.ts +2 -1
- package/dist/ui/elements/stat.js +3 -3
- package/dist/ui/elements/stat.tsx +2 -1
- package/dist/ui/elements/stat.works.js +1 -1
- package/dist/ui/elements/surface.d.ts +2 -1
- package/dist/ui/elements/surface.js +2 -2
- package/dist/ui/elements/surface.tsx +2 -1
- package/dist/ui/elements/surface.works.js +1 -1
- package/dist/ui/elements/table.d.ts +2 -1
- package/dist/ui/elements/table.js +4 -4
- package/dist/ui/elements/table.tsx +2 -1
- package/dist/ui/elements/table.works.js +1 -1
- package/dist/ui/elements/tabs.d.ts +2 -1
- package/dist/ui/elements/tabs.js +4 -4
- package/dist/ui/elements/tabs.tsx +2 -1
- package/dist/ui/elements/tabs.works.js +1 -1
- package/dist/ui/elements/text.d.ts +2 -1
- package/dist/ui/elements/text.js +2 -2
- package/dist/ui/elements/text.tsx +2 -1
- package/dist/ui/elements/textarea.d.ts +2 -1
- package/dist/ui/elements/textarea.js +4 -4
- package/dist/ui/elements/textarea.tsx +2 -1
- package/dist/ui/elements/time.d.ts +3 -1
- package/dist/ui/elements/time.js +7 -4
- package/dist/ui/elements/time.tsx +9 -2
- package/dist/ui/elements/time.works.d.ts +1 -0
- package/dist/ui/elements/time.works.js +7 -3
- package/dist/ui/elements/time.works.ts +6 -2
- package/dist/ui/elements/toggle.d.ts +2 -1
- package/dist/ui/elements/toggle.js +3 -3
- package/dist/ui/elements/toggle.tsx +2 -1
- package/dist/ui/elements/tooltip.d.ts +2 -1
- package/dist/ui/elements/tooltip.js +2 -2
- package/dist/ui/elements/tooltip.tsx +2 -1
- package/dist/ui/elements/tooltip.works.js +2 -2
- package/dist/ui/elements/value.d.ts +3 -1
- package/dist/ui/elements/value.js +10 -5
- package/dist/ui/elements/value.tsx +12 -3
- package/dist/ui/elements/value.works.js +1 -1
- package/dist/ui/elements.d.ts +15 -11
- package/dist/ui/elements.js +6 -6
- package/dist/ui/elements.ts +7 -7
- package/dist/ui/fonts.d.ts +1 -1
- package/dist/ui/fonts.js +1 -1
- package/dist/ui/generators/dev.d.ts +1 -2
- package/dist/ui/generators/dev.js +33 -37
- package/dist/ui/generators/dev.ts +30 -37
- package/dist/ui/generators/elements.d.ts +1 -0
- package/dist/ui/generators/elements.js +11 -9
- package/dist/ui/generators/elements.ts +12 -9
- package/dist/ui/generators/i18n.js +6 -23
- package/dist/ui/generators/i18n.ts +1 -18
- package/dist/ui/generators/icons.js +5 -14
- package/dist/ui/generators/icons.ts +1 -10
- package/dist/ui/generators/react.js +4 -5
- package/dist/ui/generators/react.ts +2 -3
- package/dist/ui/generators/routes.js +66 -51
- package/dist/ui/generators/routes.ts +67 -46
- package/dist/ui/generators/show.js +9 -6
- package/dist/ui/generators/show.ts +4 -2
- package/dist/ui/generators/styles.js +12 -16
- package/dist/ui/generators/styles.ts +1 -5
- package/dist/ui/generators/variants.js +3 -3
- package/dist/ui/generators/variants.ts +1 -1
- package/dist/ui/i18n/catalogs/en.js +1 -1
- package/dist/ui/i18n/format.js +1 -1
- package/dist/ui/i18n/translator.js +2 -2
- package/dist/ui/material.d.ts +11 -11
- package/dist/ui/material.js +1 -1
- package/dist/ui/materials/mat_bordered.d.ts +13 -13
- package/dist/ui/materials/mat_bordered.js +1 -1
- package/dist/ui/materials/mat_ceramic.d.ts +13 -13
- package/dist/ui/materials/mat_ceramic.js +1 -1
- package/dist/ui/materials/mat_clay.d.ts +13 -13
- package/dist/ui/materials/mat_clay.js +1 -1
- package/dist/ui/materials/mat_elevated.d.ts +13 -13
- package/dist/ui/materials/mat_elevated.js +1 -1
- package/dist/ui/materials/mat_flat.d.ts +25 -25
- package/dist/ui/materials/mat_flat.js +1 -1
- package/dist/ui/materials/mat_glass.d.ts +17 -17
- package/dist/ui/materials/mat_glass.js +1 -1
- package/dist/ui/materials/mat_lit.d.ts +13 -13
- package/dist/ui/materials/mat_lit.js +1 -1
- package/dist/ui/materials/mat_moulded.d.ts +9 -9
- package/dist/ui/materials/mat_moulded.js +1 -1
- package/dist/ui/materials.d.ts +30 -30
- package/dist/ui/materials.js +3 -3
- package/dist/ui/materials.ts +1 -1
- package/dist/ui/modes.d.ts +2 -2
- package/dist/ui/presented.js +5 -5
- package/dist/ui/provider.d.ts +3 -0
- package/dist/ui/provider.js +9 -10
- package/dist/ui/provider.tsx +7 -5
- package/dist/ui/screens.js +6 -6
- package/dist/ui/screens.ts +1 -1
- package/dist/ui/scroll-lock.js +1 -1
- package/dist/ui/series.js +1 -1
- package/dist/ui/styleguide.js +1 -1
- package/dist/ui/tokens.d.ts +2 -2
- package/dist/ui/tokens.js +3 -3
- package/dist/ui/tokens.ts +1 -1
- package/dist/ui/validators.js +1 -1
- package/dist/ui/variant.d.ts +37 -37
- package/dist/ui/variant.js +2 -2
- package/dist/ui/variants/var_avatar-round.d.ts +13 -13
- package/dist/ui/variants/var_avatar-round.js +1 -1
- package/dist/ui/variants/var_bubble-me.d.ts +12 -12
- package/dist/ui/variants/var_bubble-me.js +1 -1
- package/dist/ui/variants/var_bubble-them.d.ts +10 -10
- package/dist/ui/variants/var_bubble-them.js +1 -1
- package/dist/ui/variants/var_button-border.d.ts +61 -61
- package/dist/ui/variants/var_button-border.js +1 -1
- package/dist/ui/variants/var_button-cta.d.ts +61 -61
- package/dist/ui/variants/var_button-cta.js +1 -1
- package/dist/ui/variants/var_button-destructive-ghost.d.ts +59 -59
- package/dist/ui/variants/var_button-destructive-ghost.js +1 -1
- package/dist/ui/variants/var_button-destructive.d.ts +61 -61
- package/dist/ui/variants/var_button-destructive.js +1 -1
- package/dist/ui/variants/var_button-fill.d.ts +61 -61
- package/dist/ui/variants/var_button-fill.js +1 -1
- package/dist/ui/variants/var_button-ghost.d.ts +59 -59
- package/dist/ui/variants/var_button-ghost.js +1 -1
- package/dist/ui/variants/var_button-glass.d.ts +40 -40
- package/dist/ui/variants/var_button-glass.js +1 -1
- package/dist/ui/variants/var_button-normal.d.ts +61 -61
- package/dist/ui/variants/var_button-normal.js +1 -1
- package/dist/ui/variants/var_button-quiet.d.ts +63 -63
- package/dist/ui/variants/var_button-quiet.js +1 -1
- package/dist/ui/variants/var_card.d.ts +17 -17
- package/dist/ui/variants/var_card.js +1 -1
- package/dist/ui/variants/var_chip.d.ts +10 -10
- package/dist/ui/variants/var_chip.js +1 -1
- package/dist/ui/variants/var_choice.d.ts +15 -15
- package/dist/ui/variants/var_choice.js +1 -1
- package/dist/ui/variants/var_framed.d.ts +5 -5
- package/dist/ui/variants/var_framed.js +1 -1
- package/dist/ui/variants/var_glyph-plain.d.ts +7 -7
- package/dist/ui/variants/var_glyph-plain.js +1 -1
- package/dist/ui/variants/var_input.d.ts +30 -30
- package/dist/ui/variants/var_input.js +1 -1
- package/dist/ui/variants/var_link-inline.d.ts +17 -17
- package/dist/ui/variants/var_link-inline.js +1 -1
- package/dist/ui/variants/var_list-row.d.ts +22 -22
- package/dist/ui/variants/var_list-row.js +1 -1
- package/dist/ui/variants/var_rule.d.ts +5 -5
- package/dist/ui/variants/var_rule.js +1 -1
- package/dist/ui/variants/var_skeleton-block.d.ts +9 -9
- package/dist/ui/variants/var_skeleton-block.js +1 -1
- package/dist/ui/variants/var_spinner-turning.d.ts +9 -9
- package/dist/ui/variants/var_spinner-turning.js +1 -1
- package/dist/ui/variants/var_stat-tile.d.ts +10 -10
- package/dist/ui/variants/var_stat-tile.js +1 -1
- package/dist/ui/variants/var_tab-bar.d.ts +14 -14
- package/dist/ui/variants/var_tab-bar.js +1 -1
- package/dist/ui/variants/var_tap-target.d.ts +11 -11
- package/dist/ui/variants/var_tap-target.js +1 -1
- package/dist/ui/variants.d.ts +88 -88
- package/dist/ui/wrappers.d.ts +7 -6
- package/dist/ui/wrappers.js +16 -14
- package/dist/ui/wrappers.tsx +21 -18
- package/package.json +2 -2
package/dist/ui/elements.ts
CHANGED
|
@@ -38,7 +38,7 @@ interface Arranging extends Threaded {
|
|
|
38
38
|
|
|
39
39
|
interface Catalogue extends Threaded {
|
|
40
40
|
readonly files: string
|
|
41
|
-
readonly
|
|
41
|
+
readonly derives?: readonly Arranging[]
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const arranging = <const T extends readonly Arranging[]>(
|
|
@@ -74,7 +74,7 @@ export const ELEMENTS = {
|
|
|
74
74
|
said: 'CLI actually generates the tsx file, and you MUST give the file its body, properly.',
|
|
75
75
|
},
|
|
76
76
|
],
|
|
77
|
-
|
|
77
|
+
derives: arranging([
|
|
78
78
|
{ id: 'accordion', at: 'thread/ui/elements/accordion.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', keeps: true, works: './accordion.works.ts', carries: { items: 'parting', initial: 'ordering', exclusive: 'flag', onChange: 'choosing' }, places: { section: { tag: 'div', each: 'items', by: 'id', gives: 'section', holds: 'header region' }, header: { tag: 'button', gives: 'header', shows: 'label' }, region: { tag: 'div', gives: 'region', shows: 'inside' } }, describes: 'Sections that open one at a time, for long material somebody reads part of.' },
|
|
79
79
|
{ id: 'aspect-ratio', at: 'thread/ui/elements/aspect-ratio.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', arranges: true, carries: { ratio: 'count' }, measures: ['ratio'], works: './aspect-ratio.works.ts', sets: { aspectRatio: 'ratio' }, describes: 'A slot that keeps its proportions whatever width it gets, so a loading picture reserves its room.' },
|
|
80
80
|
{ id: 'avatar', at: 'thread/ui/elements/avatar.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', reads: 'picture', carries: { labelKey: 'naming', src: 'picture', initials: 'words' }, works: './avatar.works.ts', places: { picture: { tag: 'img', shows: 'src' }, lettering: { shows: 'lettering' } }, describes: 'A person or an organisation as a small picture, falling back to initials when there is no image.' },
|
|
@@ -82,7 +82,7 @@ export const ELEMENTS = {
|
|
|
82
82
|
{ id: 'bars', at: 'thread/ui/elements/bars.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', reads: 'picture', keeps: true, works: './bars.works.ts', carries: { rows: 'listing', by: 'ruling', value: 'ruling', format: 'words', titleKey: 'titling' }, places: { bar: { each: 'rows', by: 'by', marks: 'sized', holds: 'label amount' }, label: { shows: 'label' }, amount: { shows: 'amount' } }, describes: 'Quantities compared across categories. The default when the horizontal axis is not time.' },
|
|
83
83
|
{ id: 'box', at: 'thread/ui/elements/box.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', arranges: true, reads: 'grouping', raises: ['scrolling'], carries: { labelKey: 'titling' }, describes: 'The declared `div`. Groups children, optionally as a landmark region, and optionally scrolls. A box that scrolls raises `scrolling`, which a variant may dress; naming one makes it a group, and leaving it unnamed leaves it a box.' },
|
|
84
84
|
{ id: 'breadcrumb', at: 'thread/ui/elements/breadcrumb.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'nav', called: 'nav.breadcrumb', works: './breadcrumb.works.ts', carries: { map: 'mapping', current: 'ruling' }, places: { step: { tag: 'Link.linkInline', each: 'steps', by: 'id', gives: 'crumb' } }, describes: 'The trail back up from wherever somebody is, derived from the route rather than assembled by hand.' },
|
|
85
|
-
{ id: 'button', at: 'thread/ui/elements/button.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'button', keeps: true, works: './button.works.tsx', wires: 'wires', carries: { label: 'wording', with: 'holding', icon: 'words', shows: 'words', type: 'words', disabled: 'flag', loading: 'flag',
|
|
85
|
+
{ id: 'button', at: 'thread/ui/elements/button.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'button', keeps: true, works: './button.works.tsx', wires: 'wires', carries: { label: 'wording', with: 'holding', icon: 'words', shows: 'words', type: 'words', disabled: 'flag', loading: 'flag', input: 'holding', navigateTo: 'words', onPress: 'doing' }, places: { symbol: { when: 'iconed', shows: 'symbol', display: 'layout-flex', alignItems: 'align-centre' }, wording: { when: 'worded', shows: 'wording' } }, describes: 'A press that runs a declared workflow or goes to a declared screen. The only way to start either from a tap.' },
|
|
86
86
|
{ id: 'column', at: 'thread/ui/elements/column.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', describes: 'Children stacked top to bottom. The vertical arrangement, and the default shape of a form.', arranges: true, sets: { display: 'layout-flex', flexDirection: 'flow-down', gap: 'space-3' } },
|
|
87
87
|
{ id: 'consent', at: 'thread/ui/elements/consent.tsx', by: 'thread elements', runs: 'thread elements --own --check', keeps: true, works: './consent.works.tsx', carries: { decision: 'judging', onDecide: 'judged', policyKey: 'noting', preselected: 'ordering' }, places: { panel: { tag: 'Box.card', holds: 'body' }, body: { tag: 'Column', holds: 'title policy list actions' }, title: { tag: 'Text', gives: 'title' }, policy: { tag: 'Text', when: 'hasPolicy', gives: 'policy' }, list: { tag: 'Column', holds: 'category' }, category: { tag: 'Box', each: 'categories', by: 'id', holds: 'words switch fixed' }, words: { tag: 'Column', holds: 'label describe' }, label: { tag: 'Text', gives: 'label' }, describe: { tag: 'Text', gives: 'describe' }, switch: { tag: 'Toggle.input', when: 'optional', gives: 'chooser' }, fixed: { tag: 'Text', when: 'required', gives: 'fixed' }, actions: { tag: 'Box', holds: 'withdraw decline accept accept-all' }, withdraw: { tag: 'Button.buttonQuiet', when: 'withdrawable', gives: 'withdraw' }, decline: { tag: 'Button.buttonBorder', gives: 'decline' }, accept: { tag: 'Button.buttonBorder', gives: 'accept' }, 'accept-all': { tag: 'Button.buttonFill', gives: 'acceptAll' } }, describes: 'The complete permission-to-collect surface, with every declared category and its decision.' },
|
|
88
88
|
{ id: 'donut', at: 'thread/ui/elements/donut.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'svg', reads: 'picture', keeps: true, works: './donut.works.ts', carries: { rows: 'listing', by: 'ruling', value: 'ruling', titleKey: 'titling' }, places: { slice: { tag: 'circle', each: 'rows', by: 'by', gives: 'slice' } }, describes: 'Parts of one whole, for a handful of slices. Beyond that the reading is worse than a bar.' },
|
|
@@ -93,14 +93,14 @@ export const ELEMENTS = {
|
|
|
93
93
|
{ id: 'gauge', at: 'thread/ui/elements/gauge.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'svg', reads: 'picture', keeps: true, works: './gauge.works.ts', carries: { value: 'count', min: 'count', max: 'count', labelKey: 'naming' }, places: { track: { tag: 'path', gives: 'track' }, needle: { tag: 'path', gives: 'needle' } }, describes: 'A single quantity against a target, where the reading is how far along rather than how much.' },
|
|
94
94
|
{ id: 'grid', at: 'thread/ui/elements/grid.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', describes: 'Children arranged, and nothing more. Its tracks are not something a row can state — the style guide binds no track property, deliberately — so this sets nothing and a caller that wants a set of equal panels wraps a `Row` instead. Kept, and its sentence made true, because the one it carried promised tracks it has never laid out: “as many equal tracks as the container allows”, on a row with no `sets` at all.', arranges: true },
|
|
95
95
|
{ id: 'heatmap', at: 'thread/ui/elements/heatmap.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', reads: 'picture', keeps: true, works: './heatmap.works.ts', carries: { rows: 'listing', by: 'ruling', x: 'ruling', y: 'ruling', value: 'words', titleKey: 'titling' }, places: { cell: { each: 'rows', by: 'by', marks: 'sized' } }, describes: 'Density across two axes, for when the pattern matters more than any single cell.' },
|
|
96
|
-
{ id: 'hit-box', at: 'thread/ui/elements/hit-box.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', arranges: true, keeps: true, works: './hit-box.works.tsx', wires: 'wires', carries: { gesture: 'ruling', tuning: 'tuning', axis: 'words', grabs: 'subjecting', accepts: 'ordering', collects: 'subjecting', dwell: 'count', moves: 'flag', onDrop: 'doing',
|
|
96
|
+
{ id: 'hit-box', at: 'thread/ui/elements/hit-box.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', arranges: true, keeps: true, works: './hit-box.works.tsx', wires: 'wires', carries: { gesture: 'ruling', tuning: 'tuning', axis: 'words', grabs: 'subjecting', accepts: 'ordering', collects: 'subjecting', dwell: 'count', moves: 'flag', onDrop: 'doing', input: 'holding' }, sets: { display: 'layout-block', touchAction: 'touch', userSelect: 'pick', visibility: 'shown', position: 'flow' }, measures: ['touch', 'pick', 'shown', 'flow', 'width', 'height', 'carry', 'grip'], places: { carried: { tag: 'div', shows: 'carried', position: 'position-pinned', left: 'space-0', top: 'space-0', width: 'width', height: 'height', transform: 'carry', transformOrigin: 'grip', pointerEvents: 'reach-through', userSelect: 'picking-off', zIndex: 'layer-carried', willChange: 'readying-movement' } }, describes: 'A region that claims a gesture, composed with anything and declared by nothing. Put it where the grab should be, not on the element.' },
|
|
97
97
|
{ id: 'glyph', at: 'thread/ui/elements/glyph.tsx', by: 'thread elements', runs: 'thread elements --own --check', carries: { name: 'ruling', labelKey: 'noting' }, works: './glyph.works.tsx', describes: 'A semantic pictogram — `close`, `search` — resolved from whichever set the design chose at build time. It is laid out rather than left inline, because an `svg` that is left inline stands on a text baseline: the line it sits on keeps a descender\'s worth of room under the drawing, so the drawing rides above the middle of whatever holds it and the wording beside it reads as sitting lower. Measured 2026-09-03 in `thread-palette-lab`: a 24px symbol in a 28px box, its centre 2.00px above the row\'s and the wording\'s 0.36px below — 2.36px apart, the same amount everywhere, on every icon in the app.', sets: { display: 'layout-flex' } },
|
|
98
98
|
{ id: 'image', at: 'thread/ui/elements/image.tsx', by: 'thread elements', runs: 'thread elements --own --check', carries: { src: 'source', altKey: 'naming' }, tag: 'img', describes: 'A picture with alternative wording, for photographs and artwork rather than for interface symbols.' },
|
|
99
99
|
{ id: 'line-chart', at: 'thread/ui/elements/line-chart.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'svg', reads: 'picture', keeps: true, works: './line-chart.works.ts', carries: { rows: 'listing', by: 'words', value: 'ruling', titleKey: 'titling' }, places: { line: { tag: 'polyline', each: 'lines', by: 'group', gives: 'drawing' } }, describes: 'A quantity over time, with axes. The chart for a trend somebody will read values off.' },
|
|
100
100
|
{ id: 'link', at: 'thread/ui/elements/link.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'a', keeps: true, shows: 'labelKey', carries: { navigateTo: 'ruling', labelKey: 'wording', is: 'styling' }, works: './link.works.ts', describes: 'Wording that carries somebody to a declared screen. A press that navigates and does not act.' },
|
|
101
101
|
{ id: 'media', at: 'thread/ui/elements/media.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: { by: 'as', image: 'img', video: 'video', audio: 'audio', file: 'a' }, carries: { as: 'words', src: 'source', altKey: 'naming' }, describes: 'Whatever a media or file value turns out to be — a picture, a clip, a document — rendered by its kind.' },
|
|
102
102
|
{ id: 'menu', at: 'thread/ui/elements/menu.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', keeps: true, works: './menu.works.tsx', dresses: 'opener', measures: ['x', 'y'], carries: { labelKey: 'wording', items: 'listing', side: 'words' }, places: { opener: { tag: 'button', gives: 'opener', shows: 'labelKey' }, portal: { tag: 'Presented', gives: 'portal', holds: 'layer' }, layer: { tag: 'div', gives: 'listing', holds: 'row', position: 'position-lifted', left: 'x', top: 'y' }, row: { tag: 'MenuRow', each: 'items', by: 'labelKey', gives: 'row' } }, describes: 'Actions revealed on demand from a trigger, with keyboard traversal and dismissal already handled.' },
|
|
103
|
-
{ id: 'money', at: 'thread/ui/elements/money.tsx', by: 'thread elements', runs: 'thread elements --own --check', carries: { value: 'amount', is: 'styling' }, works: './money.works.ts', describes: 'An amount with its currency, formatted for the locale. Never a number and a symbol concatenated.' },
|
|
103
|
+
{ id: 'money', at: 'thread/ui/elements/money.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', raises: ['invalid'], carries: { value: 'amount', is: 'styling' }, works: './money.works.ts', describes: 'An amount with its currency, formatted for the locale. Never a number and a symbol concatenated.' },
|
|
104
104
|
{ id: 'pagination', at: 'thread/ui/elements/pagination.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'nav', called: 'nav.pagination', works: './pagination.works.ts', carries: { map: 'mapping', sequence: 'ordering', current: 'ruling' }, places: { step: { tag: 'Link.linkInline', each: 'steps', by: 'id', gives: 'step' } }, describes: 'Movement between pages of a long result, for when scrolling to the end is not reasonable.' },
|
|
105
105
|
{ id: 'progress', at: 'thread/ui/elements/progress.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', reads: 'progress', carries: { value: 'count', target: 'count', labelKey: 'naming' }, measures: ['fraction', 'span'], works: './progress.works.ts', places: { bar: { width: 'span' } }, describes: 'Work advancing toward a known end. The determinate half of what a spinner shows.' },
|
|
106
106
|
{ id: 'radio-group', at: 'thread/ui/elements/radio-group.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', reads: 'choices', keeps: true, works: './radio-group.works.ts', carries: { name: 'ruling', labelKey: 'naming', options: 'listing', chosen: 'words', onValue: 'picking', disabled: 'flag' }, places: { option: { tag: 'label', each: 'options', by: 'value', holds: 'control caption' }, control: { tag: 'input', reads: 'choosing', gives: 'option' }, caption: { shows: 'caption' } }, describes: 'One answer from a short set, all visible at once. The version of a picker with nothing hidden.' },
|
|
@@ -119,9 +119,9 @@ export const ELEMENTS = {
|
|
|
119
119
|
{ id: 'tabs', at: 'thread/ui/elements/tabs.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'div', keeps: true, works: './tabs.works.ts', carries: { items: 'parting', initial: 'words', onChange: 'picking', orientation: 'words' }, dresses: 'strip', places: { strip: { tag: 'div', gives: 'strip', holds: 'tab', display: 'layout-flex', alignItems: 'align-centre' }, tab: { tag: 'button', each: 'items', by: 'id', gives: 'tab', shows: 'label', display: 'layout-flex', alignItems: 'align-centre', justifyContent: 'align-centre', height: 'control-md', padding: 'control-pad-y control-pad-x', borderRadius: 'control-radius', borderWidth: 'edge-hair', borderStyle: 'line-off', borderColor: 'colour-clear', background: 'colour-clear', color: 'colour-ink', cursor: 'cursor-press', transition: 'motion-quick ease-out' }, panel: { tag: 'div', when: 'opened', gives: 'panel', shows: 'inside' } }, describes: 'Sibling panels where only one is visible, switched without going anywhere. Not navigation between screens. It `dresses` its strip rather than itself, which is the difference between a row of tabs and a capsule: the element wraps the strip AND the panel, so a variant landing on the element put `radius-lg` and a filled material around the whole assembly — measured 2026-09-03, and it is what made tabs read as one pill with a page inside it. The tab itself states its own look here because every key it had was structural, so the generator emitted a `button` with no class at all and nothing any style guide row said could reach it.' },
|
|
120
120
|
{ id: 'text', at: 'thread/ui/elements/text.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', shows: 'k', sets: { display: 'layout-inline-block' }, carries: { k: 'wording', with: 'holding', is: 'styling', announce: 'announcing' }, describes: 'The only place a message key becomes visible words. Every sentence a person reads passes through here. It sets `inline-block` because that is what makes a font role\'s trim reach it: `text-box-trim` acts on a block container\'s line boxes and an inline box is not one, so a role carrying the trim governed nothing wherever wording rendered inline. Measured 2026-09-03 at a 1280 viewport: 62 of 90 text elements trimmed to their cap height exactly without this, 72 with it, and the number wrapping to more than one line was 4 either way — it costs no wrapping. Inline-block rather than block because wording still belongs in a line beside other things when an app puts it there.' },
|
|
121
121
|
{ id: 'textarea', at: 'thread/ui/elements/textarea.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'label', carries: { labelKey: 'wording', name: 'words', value: 'words', onValue: 'picking', validator: 'words', rows: 'count' }, works: './textarea.works.ts', places: { label: { shows: 'labelKey' }, control: { tag: 'textarea', reads: 'writing', shows: 'value', takes: 'name rows validator' } }, describes: 'Prose being typed across more than one line, where the shape of what is written matters.' },
|
|
122
|
-
{ id: 'time', at: 'thread/ui/elements/time.tsx', by: 'thread elements', runs: 'thread elements --own --check', keeps: true, carries: { value: 'amount', relative: 'flag', is: 'styling' }, works: './time.works.ts', describes: 'A moment, formatted absolutely or as distance from now. Never a date somebody assembled from parts.' },
|
|
122
|
+
{ id: 'time', at: 'thread/ui/elements/time.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', raises: ['invalid'], keeps: true, carries: { value: 'amount', relative: 'flag', is: 'styling' }, works: './time.works.ts', describes: 'A moment, formatted absolutely or as distance from now. Never a date somebody assembled from parts.' },
|
|
123
123
|
{ id: 'toggle', at: 'thread/ui/elements/toggle.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'label', carries: { labelKey: 'wording', on: 'flag', onToggle: 'telling' }, works: './toggle.works.ts', places: { control: { tag: 'input', reads: 'choice', shows: 'on' }, label: { shows: 'labelKey' } }, describes: 'A single yes-or-no answer, flipped in place. Reach for this rather than two mutually exclusive buttons.' },
|
|
124
124
|
{ id: 'tooltip', at: 'thread/ui/elements/tooltip.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', arranges: true, keeps: true, works: './tooltip.works.ts', dresses: 'bubble', measures: ['x', 'y'], carries: { labelKey: 'wording', side: 'words' }, places: { bubble: { tag: 'span', when: 'showing', gives: 'bubble', shows: 'labelKey', position: 'position-lifted', left: 'x', top: 'y' } }, describes: 'A short clarification revealed by hovering or focusing. Never the only place something is said.' },
|
|
125
|
-
{ id: 'value', at: 'thread/ui/elements/value.tsx', by: 'thread elements', runs: 'thread elements --own --check', carries: { validator: 'ruling', value: 'amount', context: 'holding', is: 'styling' }, works: './value.works.ts', describes: 'A single formatted datum, punctuated by the validator that governs its kind rather than by the caller.' },
|
|
125
|
+
{ id: 'value', at: 'thread/ui/elements/value.tsx', by: 'thread elements', runs: 'thread elements --own --check', tag: 'span', raises: ['invalid'], carries: { validator: 'ruling', value: 'amount', context: 'holding', is: 'styling' }, works: './value.works.ts', describes: 'A single formatted datum, punctuated by the validator that governs its kind rather than by the caller.' },
|
|
126
126
|
]),
|
|
127
127
|
} as const satisfies Catalogue
|
package/dist/ui/fonts.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Threaded } from '@prestack/thread/core/thread';
|
|
2
2
|
import { type Holds } from './token.ts';
|
|
3
|
-
export declare const FACES: readonly [
|
|
3
|
+
export declare const FACES: readonly ['body', 'heading', 'display', 'mono'];
|
|
4
4
|
export type Face = (typeof FACES)[number];
|
|
5
5
|
export interface FontFamily {
|
|
6
6
|
readonly name: string;
|
package/dist/ui/fonts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sheeted } from
|
|
1
|
+
import { sheeted } from './token.js';
|
|
2
2
|
export const FACES = ['body', 'heading', 'display', 'mono'];
|
|
3
3
|
const SANS_FALLBACK = 'ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif';
|
|
4
4
|
const SERIF_FALLBACK = 'ui-serif, Georgia, Cambria, Times New Roman, serif';
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export declare function
|
|
2
|
-
export declare function unrunnable(entry: string, said: string | undefined): string | undefined;
|
|
1
|
+
export declare function unrunnable(program: string): string;
|
|
@@ -1,62 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
const
|
|
1
|
+
import { releases, starts } from '@prestack/thread/integrations/cli';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { argv, execPath, stdout } from 'node:process';
|
|
4
|
+
import { generators, settling } from '@prestack/thread/core/chain';
|
|
5
|
+
import { existsSync } from '@prestack/thread/integrations/disk';
|
|
6
|
+
import { CLI, exit, invoked } from '@prestack/thread/core/invoked';
|
|
7
|
+
import { named } from '@prestack/thread/core/scope';
|
|
8
|
+
import { findTree } from '@prestack/thread/core/tree';
|
|
9
|
+
import { programOf } from '@prestack/thread/integrations/target';
|
|
10
|
+
const SERVER = 'next';
|
|
11
11
|
const SHIP = '--ship';
|
|
12
12
|
const ENTRY = 'dev';
|
|
13
13
|
const SHIPPED = 'build';
|
|
14
|
+
const GATE = 'gate';
|
|
14
15
|
const say = (line) => {
|
|
15
|
-
|
|
16
|
+
stdout.write(line);
|
|
16
17
|
};
|
|
17
|
-
export function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return undefined;
|
|
21
|
-
return JSON.parse(source).scripts?.[entry];
|
|
22
|
-
}
|
|
23
|
-
export function unrunnable(entry, said) {
|
|
24
|
-
if (said !== undefined)
|
|
25
|
-
return undefined;
|
|
26
|
-
return (`this app's ${MANIFEST} declares no \`${entry}\` script, so there is nothing here to run. ` +
|
|
27
|
-
`\`thread attach ${named('integrity')}\` is what writes it, along with the \`thread gate\` hook that ` +
|
|
28
|
-
'stands in front of it — run the attach and try again.');
|
|
18
|
+
export function unrunnable(program) {
|
|
19
|
+
return (program + ' is not installed here, so there is nothing to serve this app with. ' +
|
|
20
|
+
named('ui') + ' brings it: run thread attach ' + named('ui') + ' and try again.');
|
|
29
21
|
}
|
|
30
22
|
async function run() {
|
|
31
23
|
const tree = findTree();
|
|
32
24
|
const shipping = argv.includes(SHIP);
|
|
33
25
|
const entry = shipping ? SHIPPED : ENTRY;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
say(`thread: ${refused}\n`);
|
|
26
|
+
const program = programOf(SERVER, tree.at, existsSync);
|
|
27
|
+
if (program === undefined) {
|
|
28
|
+
say('thread: ' + unrunnable(SERVER) + '\n');
|
|
38
29
|
return 1;
|
|
39
30
|
}
|
|
40
31
|
const settled = settling(tree.at, await generators(tree));
|
|
41
32
|
for (const said of settled.ran)
|
|
42
33
|
if (said.trim() !== '')
|
|
43
|
-
say(
|
|
34
|
+
say(said + '\n');
|
|
44
35
|
if (settled.unsettled.length > 0) {
|
|
45
|
-
say(
|
|
36
|
+
say('thread: ' + settled.unsettled.join(', ') + ' would not settle after ' + String(settled.rounds) + ' rounds, ' +
|
|
46
37
|
'so what this tree declares and what is written from it still disagree. Nothing is served over that.\n');
|
|
47
38
|
return 1;
|
|
48
39
|
}
|
|
49
|
-
say(
|
|
40
|
+
say('thread: ' + basename(tree.at) + ' settled in ' + String(settled.rounds) + ' round(s); running the gate\n');
|
|
41
|
+
const gated = starts(execPath, [...CLI, GATE], { at: tree.at, shows: true });
|
|
42
|
+
if (!gated.ok) {
|
|
43
|
+
say('thread: the gate refused this tree, and nothing is served over a red gate. What it refused is printed above.\n');
|
|
44
|
+
return gated.code === 0 ? 1 : gated.code;
|
|
45
|
+
}
|
|
46
|
+
say('thread: the gate passed; running ' + SERVER + ' ' + entry + '\n');
|
|
50
47
|
const extra = argv.slice(2).filter((one) => one !== SHIP);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
say(`thread: could not start \`npm run ${entry}\` — ${held.error.message}\n`);
|
|
48
|
+
if (!shipping)
|
|
49
|
+
releases();
|
|
50
|
+
const held = starts(program, [entry, ...extra], { at: tree.at, shows: true });
|
|
51
|
+
if (!held.there) {
|
|
52
|
+
say('thread: could not start ' + SERVER + ' ' + entry + ': ' + held.why + '\n');
|
|
57
53
|
return 1;
|
|
58
54
|
}
|
|
59
|
-
return held.
|
|
55
|
+
return held.code;
|
|
60
56
|
}
|
|
61
57
|
if (invoked(import.meta.url))
|
|
62
58
|
exit(await run());
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { argv } from 'node:process'
|
|
1
|
+
import { releases, starts } from '@prestack/thread/integrations/cli'
|
|
2
|
+
import { basename } from 'node:path'
|
|
3
|
+
import { argv, execPath, stdout } from 'node:process'
|
|
5
4
|
|
|
6
5
|
import { generators, settling } from '@prestack/thread/core/chain'
|
|
7
|
-
import { existsSync
|
|
8
|
-
import { exit, invoked } from '@prestack/thread/core/invoked'
|
|
6
|
+
import { existsSync } from '@prestack/thread/integrations/disk'
|
|
7
|
+
import { CLI, exit, invoked } from '@prestack/thread/core/invoked'
|
|
9
8
|
import { named } from '@prestack/thread/core/scope'
|
|
10
9
|
import { findTree } from '@prestack/thread/core/tree'
|
|
10
|
+
import { programOf } from '@prestack/thread/integrations/target'
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const SERVER = 'next'
|
|
13
13
|
|
|
14
14
|
const SHIP = '--ship'
|
|
15
15
|
|
|
@@ -17,26 +17,16 @@ const ENTRY = 'dev'
|
|
|
17
17
|
|
|
18
18
|
const SHIPPED = 'build'
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
writeSync(1, line)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface Manifest {
|
|
25
|
-
readonly scripts?: Record<string, string>
|
|
26
|
-
}
|
|
20
|
+
const GATE = 'gate'
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (source === undefined) return undefined
|
|
31
|
-
return (JSON.parse(source) as Manifest).scripts?.[entry]
|
|
22
|
+
const say = (line: string): void => {
|
|
23
|
+
stdout.write(line)
|
|
32
24
|
}
|
|
33
25
|
|
|
34
|
-
export function unrunnable(
|
|
35
|
-
if (said !== undefined) return undefined
|
|
26
|
+
export function unrunnable(program: string): string {
|
|
36
27
|
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'stands in front of it — run the attach and try again.'
|
|
28
|
+
program + ' is not installed here, so there is nothing to serve this app with. ' +
|
|
29
|
+
named('ui') + ' brings it: run thread attach ' + named('ui') + ' and try again.'
|
|
40
30
|
)
|
|
41
31
|
}
|
|
42
32
|
|
|
@@ -44,37 +34,40 @@ async function run(): Promise<number> {
|
|
|
44
34
|
const tree = findTree()
|
|
45
35
|
const shipping = argv.includes(SHIP)
|
|
46
36
|
const entry = shipping ? SHIPPED : ENTRY
|
|
47
|
-
const read = (where: string): string | undefined => (existsSync(where) ? readFileSync(where, 'utf8') : undefined)
|
|
48
37
|
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
say(
|
|
38
|
+
const program = programOf(SERVER, tree.at, existsSync)
|
|
39
|
+
if (program === undefined) {
|
|
40
|
+
say('thread: ' + unrunnable(SERVER) + '\n')
|
|
52
41
|
return 1
|
|
53
42
|
}
|
|
54
43
|
|
|
55
44
|
const settled = settling(tree.at, await generators(tree))
|
|
56
|
-
for (const said of settled.ran) if (said.trim() !== '') say(
|
|
45
|
+
for (const said of settled.ran) if (said.trim() !== '') say(said + '\n')
|
|
57
46
|
|
|
58
47
|
if (settled.unsettled.length > 0) {
|
|
59
48
|
say(
|
|
60
|
-
|
|
49
|
+
'thread: ' + settled.unsettled.join(', ') + ' would not settle after ' + String(settled.rounds) + ' rounds, ' +
|
|
61
50
|
'so what this tree declares and what is written from it still disagree. Nothing is served over that.\n',
|
|
62
51
|
)
|
|
63
52
|
return 1
|
|
64
53
|
}
|
|
65
54
|
|
|
66
|
-
say(
|
|
55
|
+
say('thread: ' + basename(tree.at) + ' settled in ' + String(settled.rounds) + ' round(s); running the gate\n')
|
|
56
|
+
const gated = starts(execPath, [...CLI, GATE], { at: tree.at, shows: true })
|
|
57
|
+
if (!gated.ok) {
|
|
58
|
+
say('thread: the gate refused this tree, and nothing is served over a red gate. What it refused is printed above.\n')
|
|
59
|
+
return gated.code === 0 ? 1 : gated.code
|
|
60
|
+
}
|
|
67
61
|
|
|
62
|
+
say('thread: the gate passed; running ' + SERVER + ' ' + entry + '\n')
|
|
68
63
|
const extra = argv.slice(2).filter((one) => one !== SHIP)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (held.error !== undefined) {
|
|
74
|
-
say(`thread: could not start \`npm run ${entry}\` — ${held.error.message}\n`)
|
|
64
|
+
if (!shipping) releases()
|
|
65
|
+
const held = starts(program, [entry, ...extra], { at: tree.at, shows: true })
|
|
66
|
+
if (!held.there) {
|
|
67
|
+
say('thread: could not start ' + SERVER + ' ' + entry + ': ' + held.why + '\n')
|
|
75
68
|
return 1
|
|
76
69
|
}
|
|
77
|
-
return held.
|
|
70
|
+
return held.code
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
if (invoked(import.meta.url)) exit(await run())
|
|
@@ -14,6 +14,7 @@ export declare function translated(name: string, tag: string | Readonly<Record<s
|
|
|
14
14
|
readonly parts: string;
|
|
15
15
|
readonly consent: string;
|
|
16
16
|
readonly drag: string;
|
|
17
|
+
readonly checked: string;
|
|
17
18
|
}, carries?: Readonly<Record<string, string>>, always?: readonly string[], places?: Readonly<Record<string, Readonly<Record<string, string>>>>, works?: string, measures?: readonly string[], reads?: string, gives?: readonly string[], keeps?: boolean, animates?: boolean, says?: string, called?: string, wires?: string, dresses?: string): string;
|
|
18
19
|
export declare const CARRIES: readonly string[];
|
|
19
20
|
export declare const READINGS: readonly string[];
|
|
@@ -6,13 +6,13 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync } from
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/integrations/disk';
|
|
10
10
|
import { join, resolve } from 'node:path';
|
|
11
11
|
import { argv, stdout } from 'node:process';
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
|
-
import { exit, invoked } from
|
|
14
|
-
import { findTree, inTree } from
|
|
15
|
-
import { camel, classOf, classOfElement, looking } from
|
|
13
|
+
import { exit, invoked } from '@prestack/thread/core/invoked';
|
|
14
|
+
import { findTree, inTree } from '@prestack/thread/core/tree';
|
|
15
|
+
import { camel, classOf, classOfElement, looking } from '../variant.js';
|
|
16
16
|
const idOf = (name) => name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
17
17
|
export function pascal(id) {
|
|
18
18
|
return id
|
|
@@ -43,7 +43,7 @@ export function translated(name, tag, arranges, raises, from, carries = {}, alwa
|
|
|
43
43
|
throw new Error(`\`${name}\` is written before the variant rows that wear it were read, so it would ship wearing none of them`);
|
|
44
44
|
}
|
|
45
45
|
const invoking = [
|
|
46
|
-
...(carries['dispatch'] === undefined ? [' readonly dispatch?:
|
|
46
|
+
...(carries['dispatch'] === undefined ? [' readonly dispatch?: keyof Dispatching'] : []),
|
|
47
47
|
...(carries['input'] === undefined ? [' readonly input?: Readonly<Record<string, unknown>>'] : []),
|
|
48
48
|
" readonly invokeOn?: 'press' | 'shown'",
|
|
49
49
|
];
|
|
@@ -320,6 +320,7 @@ ${[
|
|
|
320
320
|
: []),
|
|
321
321
|
].join('\n')}
|
|
322
322
|
|
|
323
|
+
import type { Dispatching } from '${from.checked}'
|
|
323
324
|
import { type Element, element, useSurface } from '${from.wrappers}'
|
|
324
325
|
${Object.values(places).some((made) => (made?.['takes'] ?? '').split(/\s+/).includes('validator')) ? `import { limited } from '${from.validators}'\n` : ''}${repeats ? `import { Each } from '../elements/each.tsx'\n` : ''}${works === undefined || wanted.length === 0 ? '' : `import { ${wanted.join(', ')} } from '${works}'\n`}
|
|
325
326
|
|
|
@@ -427,7 +428,7 @@ const READS = {
|
|
|
427
428
|
};
|
|
428
429
|
export const CARRIES = Object.keys(KINDS);
|
|
429
430
|
export const READINGS = Object.keys(READS);
|
|
430
|
-
const BESIDE = { wrappers: '../wrappers.tsx', content: '../content.tsx', rows: '../charts.tsx', validators: '../validators.ts', fonts: '../fonts.ts', map: '../islands/map.ts', parts: '../disclosure.tsx', consent: '../consent.ts', drag: '../drag.ts' };
|
|
431
|
+
const BESIDE = { wrappers: '../wrappers.tsx', content: '../content.tsx', rows: '../charts.tsx', validators: '../validators.ts', fonts: '../fonts.ts', map: '../islands/map.ts', parts: '../disclosure.tsx', consent: '../consent.ts', drag: '../drag.ts', checked: '../checked.ts' };
|
|
431
432
|
export const declared = new Set();
|
|
432
433
|
export const wears = new Map();
|
|
433
434
|
const SHEET = `elements.${'ts'}`;
|
|
@@ -473,10 +474,11 @@ async function run() {
|
|
|
473
474
|
}
|
|
474
475
|
const root = findTree().at;
|
|
475
476
|
const from = BESIDE;
|
|
476
|
-
|
|
477
|
+
const listed = sheet.ELEMENT.derives ?? sheet.ELEMENT.declares ?? [];
|
|
478
|
+
for (const one of listed)
|
|
477
479
|
declared.add(one.id);
|
|
478
480
|
const dressed = await variants(own);
|
|
479
|
-
for (const one of
|
|
481
|
+
for (const one of listed) {
|
|
480
482
|
const held = {};
|
|
481
483
|
for (const row of dressed) {
|
|
482
484
|
if (!row.wears.includes(pascal(one.id)))
|
|
@@ -485,7 +487,7 @@ async function run() {
|
|
|
485
487
|
}
|
|
486
488
|
wears.set(one.id, held);
|
|
487
489
|
}
|
|
488
|
-
const rows =
|
|
490
|
+
const rows = listed.filter((row) => derived(row, own));
|
|
489
491
|
const checking = argv.includes('--check');
|
|
490
492
|
for (const row of rows) {
|
|
491
493
|
if (row.at === undefined) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/integrations/disk'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
3
|
import { argv, stdout } from 'node:process'
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
@@ -41,7 +41,7 @@ export function translated(
|
|
|
41
41
|
tag: string | Readonly<Record<string, string>>,
|
|
42
42
|
arranges: boolean,
|
|
43
43
|
raises: readonly string[],
|
|
44
|
-
from: { readonly wrappers: string; readonly content: string; readonly rows: string; readonly validators: string; readonly fonts: string; readonly map: string; readonly parts: string; readonly consent: string; readonly drag: string },
|
|
44
|
+
from: { readonly wrappers: string; readonly content: string; readonly rows: string; readonly validators: string; readonly fonts: string; readonly map: string; readonly parts: string; readonly consent: string; readonly drag: string; readonly checked: string },
|
|
45
45
|
carries: Readonly<Record<string, string>> = {},
|
|
46
46
|
always: readonly string[] = [],
|
|
47
47
|
places: Readonly<Record<string, Readonly<Record<string, string>>>> = {},
|
|
@@ -66,7 +66,7 @@ export function translated(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const invoking = [
|
|
69
|
-
...(carries['dispatch'] === undefined ? [' readonly dispatch?:
|
|
69
|
+
...(carries['dispatch'] === undefined ? [' readonly dispatch?: keyof Dispatching'] : []),
|
|
70
70
|
...(carries['input'] === undefined ? [' readonly input?: Readonly<Record<string, unknown>>'] : []),
|
|
71
71
|
" readonly invokeOn?: 'press' | 'shown'",
|
|
72
72
|
]
|
|
@@ -430,6 +430,7 @@ ${[
|
|
|
430
430
|
: []),
|
|
431
431
|
].join('\n')}
|
|
432
432
|
|
|
433
|
+
import type { Dispatching } from '${from.checked}'
|
|
433
434
|
import { type Element, element, useSurface } from '${from.wrappers}'
|
|
434
435
|
${Object.values(places).some((made) => ((made as Record<string, string> | undefined)?.['takes'] ?? '').split(/\s+/).includes('validator')) ? `import { limited } from '${from.validators}'\n` : ''}${repeats ? `import { Each } from '../elements/each.tsx'\n` : ''}${works === undefined || wanted.length === 0 ? '' : `import { ${wanted.join(', ')} } from '${works}'\n`}
|
|
435
436
|
|
|
@@ -601,7 +602,7 @@ export const CARRIES: readonly string[] = Object.keys(KINDS)
|
|
|
601
602
|
|
|
602
603
|
export const READINGS: readonly string[] = Object.keys(READS)
|
|
603
604
|
|
|
604
|
-
const BESIDE = { wrappers: '../wrappers.tsx', content: '../content.tsx', rows: '../charts.tsx', validators: '../validators.ts', fonts: '../fonts.ts', map: '../islands/map.ts', parts: '../disclosure.tsx', consent: '../consent.ts', drag: '../drag.ts' } as const
|
|
605
|
+
const BESIDE = { wrappers: '../wrappers.tsx', content: '../content.tsx', rows: '../charts.tsx', validators: '../validators.ts', fonts: '../fonts.ts', map: '../islands/map.ts', parts: '../disclosure.tsx', consent: '../consent.ts', drag: '../drag.ts', checked: '../checked.ts' } as const
|
|
605
606
|
|
|
606
607
|
interface Row {
|
|
607
608
|
readonly id: string
|
|
@@ -664,7 +665,7 @@ async function run(): Promise<number> {
|
|
|
664
665
|
|
|
665
666
|
const held = own
|
|
666
667
|
? ((await import(pathToFileURL(resolve(fileURLToPath(import.meta.url), '../../elements.ts')).href)) as {
|
|
667
|
-
ELEMENTS?: { readonly declares?: readonly Row[] }
|
|
668
|
+
ELEMENTS?: { readonly derives?: readonly Row[]; readonly declares?: readonly Row[] }
|
|
668
669
|
})
|
|
669
670
|
: undefined
|
|
670
671
|
|
|
@@ -681,7 +682,7 @@ async function run(): Promise<number> {
|
|
|
681
682
|
tree === undefined
|
|
682
683
|
? { ELEMENT: held?.ELEMENTS }
|
|
683
684
|
: ((await import(pathToFileURL(inTree(tree, SHEET)).href)) as {
|
|
684
|
-
ELEMENT?: { readonly declares?: readonly Row[] }
|
|
685
|
+
ELEMENT?: { readonly derives?: readonly Row[]; readonly declares?: readonly Row[] }
|
|
685
686
|
})
|
|
686
687
|
|
|
687
688
|
if (sheet.ELEMENT === undefined) {
|
|
@@ -696,10 +697,12 @@ async function run(): Promise<number> {
|
|
|
696
697
|
const root = findTree().at
|
|
697
698
|
const from = BESIDE
|
|
698
699
|
|
|
699
|
-
|
|
700
|
+
const listed = sheet.ELEMENT.derives ?? sheet.ELEMENT.declares ?? []
|
|
701
|
+
|
|
702
|
+
for (const one of listed) declared.add(one.id)
|
|
700
703
|
|
|
701
704
|
const dressed = await variants(own)
|
|
702
|
-
for (const one of
|
|
705
|
+
for (const one of listed) {
|
|
703
706
|
const held: Record<string, string> = {}
|
|
704
707
|
for (const row of dressed) {
|
|
705
708
|
if (!row.wears.includes(pascal(one.id))) continue
|
|
@@ -708,7 +711,7 @@ async function run(): Promise<number> {
|
|
|
708
711
|
wears.set(one.id, held)
|
|
709
712
|
}
|
|
710
713
|
|
|
711
|
-
const rows =
|
|
714
|
+
const rows = listed.filter((row) => derived(row, own))
|
|
712
715
|
const checking = argv.includes('--check')
|
|
713
716
|
|
|
714
717
|
for (const row of rows) {
|
|
@@ -6,14 +6,14 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from '@prestack/thread/integrations/disk';
|
|
10
10
|
import { join, relative } from 'node:path';
|
|
11
11
|
import { argv, stderr, stdout } from 'node:process';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
|
-
import { exit, invoked } from
|
|
14
|
-
import { named as packageOf } from
|
|
15
|
-
import { findTree, inTree } from
|
|
16
|
-
import { fileFor, languagesIn, sourceIn } from
|
|
13
|
+
import { exit, invoked } from '@prestack/thread/core/invoked';
|
|
14
|
+
import { named as packageOf } from '@prestack/thread/core/scope';
|
|
15
|
+
import { findTree, inTree } from '@prestack/thread/core/tree';
|
|
16
|
+
import { fileFor, languagesIn, sourceIn } from '../localizations.js';
|
|
17
17
|
const GENERATED = `app/_generated/wording.${'ts'}`;
|
|
18
18
|
const SKIP = new Set(['node_modules', '.next', '.git', 'dist', '_generated']);
|
|
19
19
|
const CODE = /\.(tsx?|jsx?)$/;
|
|
@@ -65,7 +65,7 @@ export function keysIn(text, props) {
|
|
|
65
65
|
return [...text.matchAll(named(props))].map((one) => one[1] ?? one[2] ?? '');
|
|
66
66
|
}
|
|
67
67
|
async function shipped(at) {
|
|
68
|
-
const held = (await import(
|
|
68
|
+
const held = (await import('../i18n/catalogs/en.js'));
|
|
69
69
|
const made = {};
|
|
70
70
|
for (const [key, entry] of Object.entries(held.en ?? {})) {
|
|
71
71
|
made[key] = typeof entry === 'string' ? entry : String(entry.text ?? '');
|
|
@@ -105,8 +105,6 @@ function moduleFor(languages, source, used, wording) {
|
|
|
105
105
|
].join('\n');
|
|
106
106
|
};
|
|
107
107
|
return [
|
|
108
|
-
'/* GENERATED by thread — do not edit. Change the wording in i18n/, or the rows, and regenerate. */',
|
|
109
|
-
'',
|
|
110
108
|
'export type Language =',
|
|
111
109
|
...languages.map((one) => ` | ${asJson(one.id)}`),
|
|
112
110
|
'',
|
|
@@ -116,9 +114,6 @@ function moduleFor(languages, source, used, wording) {
|
|
|
116
114
|
...languages.map((one) => ` { id: ${asJson(one.id)}, name: ${asJson(one.name)}, flag: ${asJson(one.flag)} },`),
|
|
117
115
|
']',
|
|
118
116
|
'',
|
|
119
|
-
'/*',
|
|
120
|
-
' * Every key this interface names.',
|
|
121
|
-
' */',
|
|
122
117
|
'export type Used =',
|
|
123
118
|
...(used.length === 0 ? [' | never'] : used.map((one) => ` | ${asJson(one)}`)),
|
|
124
119
|
'',
|
|
@@ -126,18 +121,6 @@ function moduleFor(languages, source, used, wording) {
|
|
|
126
121
|
...languages.map((one) => catalogue(one.id)),
|
|
127
122
|
'} as const',
|
|
128
123
|
'',
|
|
129
|
-
'/*',
|
|
130
|
-
' * AN UNANSWERED KEY IS A TYPE ERROR, NAMING THE LANGUAGE AND THE KEY.',
|
|
131
|
-
' *',
|
|
132
|
-
' * `Filled` is what makes the compiler one of the surfaces that refuses rather than a formality.',
|
|
133
|
-
' * The catalogue above is `as const`, so every value is a literal type — an empty one is the',
|
|
134
|
-
' * literal `""`, which `Filled` maps to `never`, which nothing is assignable to. So a key some',
|
|
135
|
-
' * language has not answered fails HERE, at the line naming both, before anything is run.',
|
|
136
|
-
' *',
|
|
137
|
-
' * Written as an assignment rather than `satisfies`, and that is deliberate: `satisfies` on the',
|
|
138
|
-
' * literal refuses the EXTRA keys too, and there are always extra ones — an app holds every',
|
|
139
|
-
' * message it has, and `Used` is only the ones its markup names.',
|
|
140
|
-
' */',
|
|
141
124
|
"type Filled<S> = S extends '' ? never : S",
|
|
142
125
|
'',
|
|
143
126
|
'type Answered<L extends Language> = {',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from '@prestack/thread/
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from '@prestack/thread/integrations/disk'
|
|
2
2
|
import { dirname, join, relative } from 'node:path'
|
|
3
3
|
import { argv, stderr, stdout } from 'node:process'
|
|
4
4
|
import { pathToFileURL } from 'node:url'
|
|
@@ -110,8 +110,6 @@ function moduleFor(
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
return [
|
|
113
|
-
'/* GENERATED by thread — do not edit. Change the wording in i18n/, or the rows, and regenerate. */',
|
|
114
|
-
'',
|
|
115
113
|
'export type Language =',
|
|
116
114
|
...languages.map((one) => ` | ${asJson(one.id)}`),
|
|
117
115
|
'',
|
|
@@ -121,9 +119,6 @@ function moduleFor(
|
|
|
121
119
|
...languages.map((one) => ` { id: ${asJson(one.id)}, name: ${asJson(one.name)}, flag: ${asJson(one.flag)} },`),
|
|
122
120
|
']',
|
|
123
121
|
'',
|
|
124
|
-
'/*',
|
|
125
|
-
' * Every key this interface names.',
|
|
126
|
-
' */',
|
|
127
122
|
'export type Used =',
|
|
128
123
|
...(used.length === 0 ? [' | never'] : used.map((one) => ` | ${asJson(one)}`)),
|
|
129
124
|
'',
|
|
@@ -131,18 +126,6 @@ function moduleFor(
|
|
|
131
126
|
...languages.map((one) => catalogue(one.id)),
|
|
132
127
|
'} as const',
|
|
133
128
|
'',
|
|
134
|
-
'/*',
|
|
135
|
-
' * AN UNANSWERED KEY IS A TYPE ERROR, NAMING THE LANGUAGE AND THE KEY.',
|
|
136
|
-
' *',
|
|
137
|
-
' * `Filled` is what makes the compiler one of the surfaces that refuses rather than a formality.',
|
|
138
|
-
' * The catalogue above is `as const`, so every value is a literal type — an empty one is the',
|
|
139
|
-
' * literal `""`, which `Filled` maps to `never`, which nothing is assignable to. So a key some',
|
|
140
|
-
' * language has not answered fails HERE, at the line naming both, before anything is run.',
|
|
141
|
-
' *',
|
|
142
|
-
' * Written as an assignment rather than `satisfies`, and that is deliberate: `satisfies` on the',
|
|
143
|
-
' * literal refuses the EXTRA keys too, and there are always extra ones — an app holds every',
|
|
144
|
-
' * message it has, and `Used` is only the ones its markup names.',
|
|
145
|
-
' */',
|
|
146
129
|
"type Filled<S> = S extends '' ? never : S",
|
|
147
130
|
'',
|
|
148
131
|
'type Answered<L extends Language> = {',
|