@exponential-at/ui 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +54 -0
- package/README.md +360 -2
- package/catalog/a11y.json +668 -0
- package/catalog/basic-map.json +104 -0
- package/catalog/code.json +58 -0
- package/catalog/core.catalog.json +4752 -0
- package/catalog/core.schema.json +22667 -0
- package/catalog/host.json +64 -0
- package/catalog/layout.json +20 -0
- package/catalog/limits.json +9 -0
- package/catalog/locale.json +48 -0
- package/catalog/macros.json +3417 -0
- package/catalog/recipes.json +61 -0
- package/catalog/strings.json +72 -0
- package/catalog/style.json +128 -0
- package/catalog/theme.schema.json +42543 -0
- package/catalog/tokens.json +47 -0
- package/conformance/README.md +217 -0
- package/conformance/baseline.ts +101 -0
- package/conformance/browser-page.ts +146 -0
- package/conformance/browser-suites.ts +683 -0
- package/conformance/compare.ts +201 -0
- package/conformance/dom.ts +96 -0
- package/conformance/dump.ts +163 -0
- package/conformance/fonts.json +154 -0
- package/conformance/manifest.json +224 -0
- package/conformance/parity-compare.ts +191 -0
- package/conformance/parity-report.schema.json +73 -0
- package/conformance/parity.ts +412 -0
- package/conformance/pixel.ts +177 -0
- package/conformance/primitives-page.ts +49 -0
- package/conformance/report.schema.json +66 -0
- package/conformance/run.ts +118 -0
- package/dist/chunks/index-98npjvsd.js +3940 -0
- package/dist/chunks/index-98npjvsd.js.map +27 -0
- package/dist/chunks/index-gwkt14w3.js +7314 -0
- package/dist/chunks/index-gwkt14w3.js.map +10 -0
- package/dist/chunks/index-njt7yzzm.js +134 -0
- package/dist/chunks/index-njt7yzzm.js.map +9 -0
- package/dist/chunks/index-we7va6v6.js +7984 -0
- package/dist/chunks/index-we7va6v6.js.map +15 -0
- package/dist/chunks/index-xrm6f7wc.js +2563 -0
- package/dist/chunks/index-xrm6f7wc.js.map +10 -0
- package/dist/conformance.js +182749 -0
- package/dist/conformance.js.map +10 -0
- package/dist/entries/prompt.js +251 -0
- package/dist/entries/prompt.js.map +11 -0
- package/dist/entries/schema.js +1725 -0
- package/dist/entries/schema.js.map +11 -0
- package/dist/host/index.js +122 -0
- package/dist/host/index.js.map +9 -0
- package/dist/index.js +3213 -0
- package/dist/index.js.map +27 -0
- package/dist/types/src/a11y.d.ts +21 -0
- package/dist/types/src/animation.d.ts +76 -0
- package/dist/types/src/basic-map.d.ts +38 -0
- package/dist/types/src/builder.d.ts +50 -0
- package/dist/types/src/catalog.d.ts +54 -0
- package/dist/types/src/catalog.described.d.ts +33 -0
- package/dist/types/src/catalog.generated.d.ts +829 -0
- package/dist/types/src/chart.d.ts +46 -0
- package/dist/types/src/code.d.ts +44 -0
- package/dist/types/src/color.d.ts +20 -0
- package/dist/types/src/conformance.d.ts +39 -0
- package/dist/types/src/connector/app.d.ts +14 -0
- package/dist/types/src/connector/connector.d.ts +77 -0
- package/dist/types/src/connector/index.d.ts +4 -0
- package/dist/types/src/css.d.ts +29 -0
- package/dist/types/src/define.d.ts +66 -0
- package/dist/types/src/direction.d.ts +16 -0
- package/dist/types/src/dynamic.d.ts +159 -0
- package/dist/types/src/entries/prompt.d.ts +11 -0
- package/dist/types/src/entries/schema.d.ts +8 -0
- package/dist/types/src/expr.d.ts +71 -0
- package/dist/types/src/extension.d.ts +4 -0
- package/dist/types/src/format.d.ts +112 -0
- package/dist/types/src/geometry.d.ts +40 -0
- package/dist/types/src/host/contract.d.ts +188 -0
- package/dist/types/src/host/decoders.d.ts +61 -0
- package/dist/types/src/host/index.d.ts +11 -0
- package/dist/types/src/host/package.d.ts +58 -0
- package/dist/types/src/host/policy.d.ts +105 -0
- package/dist/types/src/host/router.d.ts +34 -0
- package/dist/types/src/host/runtime.d.ts +205 -0
- package/dist/types/src/host/sources.d.ts +15 -0
- package/dist/types/src/host/transports.d.ts +109 -0
- package/dist/types/src/index.d.ts +63 -0
- package/dist/types/src/layout.d.ts +20 -0
- package/dist/types/src/limits.d.ts +21 -0
- package/dist/types/src/list.d.ts +133 -0
- package/dist/types/src/locale.d.ts +22 -0
- package/dist/types/src/macros.d.ts +29 -0
- package/dist/types/src/overlay.d.ts +27 -0
- package/dist/types/src/prompt.d.ts +44 -0
- package/dist/types/src/recipes.d.ts +17 -0
- package/dist/types/src/reducer.d.ts +41 -0
- package/dist/types/src/resizable.d.ts +38 -0
- package/dist/types/src/schema.d.ts +7 -0
- package/dist/types/src/strings.d.ts +13 -0
- package/dist/types/src/style.d.ts +201 -0
- package/dist/types/src/test-preload.d.ts +1 -0
- package/dist/types/src/text-shimmer.d.ts +24 -0
- package/dist/types/src/theme-types.d.ts +162 -0
- package/dist/types/src/theme.d.ts +73 -0
- package/dist/types/src/themes.d.ts +22 -0
- package/dist/types/src/tree-guides.d.ts +17 -0
- package/dist/types/src/types.d.ts +314 -0
- package/dist/types/src/validate.d.ts +31 -0
- package/dist/types/src/web/base-css.d.ts +1 -0
- package/dist/types/src/web/box-css.d.ts +55 -0
- package/dist/types/src/web/coerce.d.ts +32 -0
- package/dist/types/src/web/data.d.ts +105 -0
- package/dist/types/src/web/dates.d.ts +12 -0
- package/dist/types/src/web/form.d.ts +44 -0
- package/dist/types/src/web/host-types.d.ts +119 -0
- package/dist/types/src/web/index.d.ts +28 -0
- package/dist/types/src/web/inputs.d.ts +55 -0
- package/dist/types/src/web/list.d.ts +13 -0
- package/dist/types/src/web/listbox.d.ts +14 -0
- package/dist/types/src/web/markdown.d.ts +96 -0
- package/dist/types/src/web/media.d.ts +62 -0
- package/dist/types/src/web/parts.d.ts +4 -0
- package/dist/types/src/web/primitives/alert-dialog.d.ts +10 -0
- package/dist/types/src/web/primitives/alert.d.ts +9 -0
- package/dist/types/src/web/primitives/avatar-color.d.ts +2 -0
- package/dist/types/src/web/primitives/avatar.d.ts +13 -0
- package/dist/types/src/web/primitives/badge.d.ts +10 -0
- package/dist/types/src/web/primitives/breadcrumb.d.ts +10 -0
- package/dist/types/src/web/primitives/button.d.ts +6 -0
- package/dist/types/src/web/primitives/calendar.d.ts +43 -0
- package/dist/types/src/web/primitives/card.d.ts +9 -0
- package/dist/types/src/web/primitives/checkbox.d.ts +5 -0
- package/dist/types/src/web/primitives/choice-tiles.d.ts +32 -0
- package/dist/types/src/web/primitives/cn.d.ts +3 -0
- package/dist/types/src/web/primitives/code-block.d.ts +24 -0
- package/dist/types/src/web/primitives/command.d.ts +134 -0
- package/dist/types/src/web/primitives/context-ring.d.ts +24 -0
- package/dist/types/src/web/primitives/dialog-arms.d.ts +6 -0
- package/dist/types/src/web/primitives/dialog.d.ts +24 -0
- package/dist/types/src/web/primitives/disclosure-header.d.ts +5 -0
- package/dist/types/src/web/primitives/dock.d.ts +42 -0
- package/dist/types/src/web/primitives/dropdown-menu.d.ts +14 -0
- package/dist/types/src/web/primitives/empty-state.d.ts +15 -0
- package/dist/types/src/web/primitives/glass-card.d.ts +4 -0
- package/dist/types/src/web/primitives/hover-card.d.ts +7 -0
- package/dist/types/src/web/primitives/icon-disc.d.ts +12 -0
- package/dist/types/src/web/primitives/index.d.ts +48 -0
- package/dist/types/src/web/primitives/input.d.ts +5 -0
- package/dist/types/src/web/primitives/kbd.d.ts +4 -0
- package/dist/types/src/web/primitives/label.d.ts +3 -0
- package/dist/types/src/web/primitives/menu-surface.d.ts +17 -0
- package/dist/types/src/web/primitives/meter.d.ts +21 -0
- package/dist/types/src/web/primitives/popover.d.ts +3 -0
- package/dist/types/src/web/primitives/progress-ring.d.ts +5 -0
- package/dist/types/src/web/primitives/progress.d.ts +8 -0
- package/dist/types/src/web/primitives/scroll-area.d.ts +49 -0
- package/dist/types/src/web/primitives/segmented-control.d.ts +5 -0
- package/dist/types/src/web/primitives/select.d.ts +47 -0
- package/dist/types/src/web/primitives/separator.d.ts +3 -0
- package/dist/types/src/web/primitives/sheet-chrome.d.ts +4 -0
- package/dist/types/src/web/primitives/sheet.d.ts +20 -0
- package/dist/types/src/web/primitives/skeleton.d.ts +3 -0
- package/dist/types/src/web/primitives/spinner.d.ts +3 -0
- package/dist/types/src/web/primitives/switch.d.ts +4 -0
- package/dist/types/src/web/primitives/table.d.ts +11 -0
- package/dist/types/src/web/primitives/tabs.d.ts +13 -0
- package/dist/types/src/web/primitives/textarea.d.ts +3 -0
- package/dist/types/src/web/primitives/toast.d.ts +163 -0
- package/dist/types/src/web/primitives/tooltip.d.ts +3 -0
- package/dist/types/src/web/primitives/truncate.d.ts +7 -0
- package/dist/types/src/web/primitives/typeahead-portal.d.ts +12 -0
- package/dist/types/src/web/share.d.ts +6 -0
- package/dist/types/src/web/table.d.ts +20 -0
- package/dist/types/src/web/theme-css.d.ts +69 -0
- package/dist/types/src/web/urls.d.ts +21 -0
- package/dist/web/index.js +2303 -0
- package/dist/web/index.js.map +26 -0
- package/dist/web/primitives/index.js +1450 -0
- package/dist/web/primitives/index.js.map +57 -0
- package/docs/components.generated.json +6238 -0
- package/docs/round-1-contract.md +679 -0
- package/docs/round-2-contract.md +476 -0
- package/docs/round-3-contract.md +174 -0
- package/docs/themes.generated.json +3503 -0
- package/fixtures/animations.json +2890 -0
- package/fixtures/bench-list.json +58 -0
- package/fixtures/bind-time.json +30664 -0
- package/fixtures/catalog-basic-map.json +1692 -0
- package/fixtures/catalog-components.json +9323 -0
- package/fixtures/catalog-extension.json +620 -0
- package/fixtures/catalog-macros.json +36370 -0
- package/fixtures/code-tokens.json +1993 -0
- package/fixtures/conformance-baseline.json +55544 -0
- package/fixtures/conformance-cases.json +49 -0
- package/fixtures/conformance-known.json +2123 -0
- package/fixtures/control-geometry.json +4200 -0
- package/fixtures/dashboard.data.json +74 -0
- package/fixtures/dashboard.json +752 -0
- package/fixtures/demo-surface.json +47 -0
- package/fixtures/format.json +870 -0
- package/fixtures/host-policy.json +1067 -0
- package/fixtures/host-router.json +1152 -0
- package/fixtures/host-transport.json +590 -0
- package/fixtures/interactions-round1.json +773 -0
- package/fixtures/kitchen-sink.expanded.json +10844 -0
- package/fixtures/kitchen-sink.json +3049 -0
- package/fixtures/layout-geometry-round1.json +7278 -0
- package/fixtures/layout-geometry.json +2072 -0
- package/fixtures/overlay-geometry.json +272 -0
- package/fixtures/primitive-specimens.json +58 -0
- package/fixtures/prompt-budget.json +33 -0
- package/fixtures/resizable.json +714 -0
- package/fixtures/round4-contract.json +2985 -0
- package/fixtures/specimens.json +9832 -0
- package/fixtures/style-conditions.json +336 -0
- package/fixtures/template-items.json +2215 -0
- package/fixtures/text-direction.json +309 -0
- package/fixtures/text-shimmer.json +96 -0
- package/fixtures/theme-extends.json +573 -0
- package/fixtures/theme-invalid.json +1296 -0
- package/fixtures/theme-recipes.json +58600 -0
- package/fixtures/theme-resolved.json +23949 -0
- package/fixtures/toast-stack.json +215 -0
- package/fixtures/tree-guides.json +224 -0
- package/fixtures/virtual-list.json +733 -0
- package/generated/ExponentialUICatalog.generated.kt +67 -0
- package/generated/ExponentialUICatalog.generated.swift +67 -0
- package/generated/ExponentialUIThemes.generated.kt +23 -0
- package/generated/ExponentialUIThemes.generated.swift +23 -0
- package/generated/catalog.descriptions.generated.rs +6 -0
- package/generated/catalog.generated.rs +80 -0
- package/generated/catalog.themes.generated.rs +34 -0
- package/generated/core.descriptions.json +2505 -0
- package/generated/core.runtime.json +3609 -0
- package/package.json +87 -4
- package/themes/exponential.recipes.json +49 -0
- package/themes/exponential.theme.json +755 -0
- package/themes/neutral.theme.json +5676 -0
- package/themes/orange.theme.json +43 -0
- package/themes/playful.theme.json +219 -0
- package/vendor/a2ui/LICENSE.txt +203 -0
- package/vendor/a2ui/README.md +27 -0
- package/vendor/a2ui/v0_9/basic_catalog.json +1387 -0
- package/vendor/a2ui/v0_9/basic_catalog_rules.txt +5 -0
- package/vendor/a2ui/v0_9/client_capabilities.json +97 -0
- package/vendor/a2ui/v0_9/client_to_server.json +104 -0
- package/vendor/a2ui/v0_9/common_types.json +315 -0
- package/vendor/a2ui/v0_9/server_to_client.json +132 -0
- package/vendor/json-render/LICENSE.txt +201 -0
- package/vendor/json-render/README.md +13 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// The COMPARATOR of the conformance harness: two frame dumps (a reference —
|
|
2
|
+
// the committed web baseline — and a candidate: a fresh web dump, the gpui
|
|
3
|
+
// dump…) node by node, matched by id.
|
|
4
|
+
//
|
|
5
|
+
// Tolerances (fixtures/conformance-cases.json `tolerance`): x, y, w, h within
|
|
6
|
+
// ±`px`; a node with text lines on either side may differ in height by up to
|
|
7
|
+
// `textLines` × its line height (one more or fewer wrapped line). Lines that
|
|
8
|
+
// differ are ALWAYS reported (`wrap`), tolerated when within `textLines`.
|
|
9
|
+
// Every node outside tolerance is a divergence:
|
|
10
|
+
// size its own w/h differ
|
|
11
|
+
// position only x/y differ
|
|
12
|
+
// wrap its line count differs by more than `textLines`
|
|
13
|
+
// and an ORIGIN when nothing below explains it (a size divergence with no
|
|
14
|
+
// size-diverging descendant; a position divergence whose parent is in
|
|
15
|
+
// tolerance) — the list to fix; the rest are its cascade. Nodes only one side
|
|
16
|
+
// placed are reported apart (`onlyRef` / `onlyCand`): the dumps' coverage
|
|
17
|
+
// differs there (e.g. a control the core expands into parts the DOM renders
|
|
18
|
+
// without `data-xui-id`), nothing was compared.
|
|
19
|
+
//
|
|
20
|
+
// The Rust gate (apps/desktop/crates/exponential-ui-gpui/tests/conformance.rs)
|
|
21
|
+
// implements the same rules.
|
|
22
|
+
|
|
23
|
+
import type { CaseDump, Dump, DumpNode } from "./dump"
|
|
24
|
+
|
|
25
|
+
export interface Tolerance {
|
|
26
|
+
px: number
|
|
27
|
+
textLines: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type DivergenceKind = `size` | `position` | `wrap`
|
|
31
|
+
|
|
32
|
+
export interface NodeDiff {
|
|
33
|
+
id: string
|
|
34
|
+
component: string
|
|
35
|
+
kinds: DivergenceKind[]
|
|
36
|
+
origin: boolean
|
|
37
|
+
ref: Pick<DumpNode, `x` | `y` | `w` | `h` | `lines` | `lh`>
|
|
38
|
+
cand: Pick<DumpNode, `x` | `y` | `w` | `h` | `lines` | `lh`>
|
|
39
|
+
d: { x: number; y: number; w: number; h: number }
|
|
40
|
+
text?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CaseReport {
|
|
44
|
+
key: string
|
|
45
|
+
matched: number
|
|
46
|
+
within: number
|
|
47
|
+
size: number
|
|
48
|
+
position: number
|
|
49
|
+
/** Wrap divergences (beyond `textLines`). */
|
|
50
|
+
wrap: number
|
|
51
|
+
/** Line-count differences within tolerance (reported, not divergences). */
|
|
52
|
+
wrapTolerated: number
|
|
53
|
+
origins: number
|
|
54
|
+
maxDelta: number
|
|
55
|
+
onlyRef: string[]
|
|
56
|
+
onlyCand: string[]
|
|
57
|
+
height: { ref: number; cand: number }
|
|
58
|
+
diffs: NodeDiff[]
|
|
59
|
+
/** Tolerated line-count changes (`id: ref → cand lines`). */
|
|
60
|
+
rewraps: string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Report {
|
|
64
|
+
ref: string
|
|
65
|
+
cand: string
|
|
66
|
+
tolerance: Tolerance
|
|
67
|
+
cases: CaseReport[]
|
|
68
|
+
missingCases: string[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const box = (n: DumpNode) => ({ x: n.x, y: n.y, w: n.w, h: n.h, ...(n.lines !== undefined ? { lines: n.lines, lh: n.lh } : {}) })
|
|
72
|
+
const r2 = (v: number) => Math.round(v * 100) / 100
|
|
73
|
+
|
|
74
|
+
export function compareCase(key: string, ref: CaseDump, cand: CaseDump, tol: Tolerance): CaseReport {
|
|
75
|
+
const refById = new Map<string, DumpNode>()
|
|
76
|
+
for (const n of ref.nodes) if (!refById.has(n.id)) refById.set(n.id, n)
|
|
77
|
+
const candById = new Map<string, DumpNode>()
|
|
78
|
+
for (const n of cand.nodes) if (!candById.has(n.id)) candById.set(n.id, n)
|
|
79
|
+
const report: CaseReport = { key, matched: 0, within: 0, size: 0, position: 0, wrap: 0, wrapTolerated: 0, origins: 0, maxDelta: 0, onlyRef: [], onlyCand: [], height: { ref: ref.height, cand: cand.height }, diffs: [], rewraps: [] }
|
|
80
|
+
const diffById = new Map<string, NodeDiff>()
|
|
81
|
+
for (const [id, a] of refById) {
|
|
82
|
+
const b = candById.get(id)
|
|
83
|
+
if (!b) {
|
|
84
|
+
report.onlyRef.push(id)
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
report.matched++
|
|
88
|
+
const d = { x: r2(b.x - a.x), y: r2(b.y - a.y), w: r2(b.w - a.w), h: r2(b.h - a.h) }
|
|
89
|
+
const lines = a.lines !== undefined || b.lines !== undefined
|
|
90
|
+
const lh = Math.max(a.lh ?? 0, b.lh ?? 0)
|
|
91
|
+
const hTol = lines ? Math.max(tol.px, tol.textLines * lh) : tol.px
|
|
92
|
+
const kinds: DivergenceKind[] = []
|
|
93
|
+
if (Math.abs(d.w) > tol.px || Math.abs(d.h) > hTol) kinds.push(`size`)
|
|
94
|
+
else if (Math.abs(d.x) > tol.px || Math.abs(d.y) > tol.px) kinds.push(`position`)
|
|
95
|
+
if (a.lines !== undefined && b.lines !== undefined && a.lines !== b.lines) {
|
|
96
|
+
if (Math.abs(a.lines - b.lines) > tol.textLines) kinds.push(`wrap`)
|
|
97
|
+
else {
|
|
98
|
+
report.wrapTolerated++
|
|
99
|
+
report.rewraps.push(`${id}: ${a.lines} → ${b.lines} lines`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
report.maxDelta = Math.max(report.maxDelta, Math.abs(d.x), Math.abs(d.y), Math.abs(d.w), Math.abs(d.h))
|
|
103
|
+
if (!kinds.length) {
|
|
104
|
+
report.within++
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
const diff: NodeDiff = { id, component: a.component, kinds, origin: false, ref: box(a), cand: box(b), d }
|
|
108
|
+
if (a.text ?? b.text) diff.text = a.text ?? b.text
|
|
109
|
+
diffById.set(id, diff)
|
|
110
|
+
report.diffs.push(diff)
|
|
111
|
+
}
|
|
112
|
+
for (const id of candById.keys()) if (!refById.has(id)) report.onlyCand.push(id)
|
|
113
|
+
// Origins: a size divergence no diverging descendant explains; a position
|
|
114
|
+
// divergence whose parent is in tolerance.
|
|
115
|
+
const sizeBelow = new Set<string>()
|
|
116
|
+
for (const diff of report.diffs) {
|
|
117
|
+
if (!diff.kinds.includes(`size`) && !diff.kinds.includes(`wrap`)) continue
|
|
118
|
+
let p = refById.get(diff.id)?.parent
|
|
119
|
+
while (p) {
|
|
120
|
+
sizeBelow.add(p)
|
|
121
|
+
p = refById.get(p)?.parent
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const diff of report.diffs) {
|
|
125
|
+
const parent = refById.get(diff.id)?.parent
|
|
126
|
+
if (diff.kinds.includes(`size`) || diff.kinds.includes(`wrap`)) diff.origin = !sizeBelow.has(diff.id)
|
|
127
|
+
else diff.origin = !parent || !diffById.has(parent)
|
|
128
|
+
if (diff.kinds.includes(`size`)) report.size++
|
|
129
|
+
if (diff.kinds.includes(`position`)) report.position++
|
|
130
|
+
if (diff.kinds.includes(`wrap`)) report.wrap++
|
|
131
|
+
if (diff.origin) report.origins++
|
|
132
|
+
}
|
|
133
|
+
return report
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function compareDumps(ref: Dump, cand: Dump, tol: Tolerance, keys?: string[]): Report {
|
|
137
|
+
const cases: CaseReport[] = []
|
|
138
|
+
const missingCases: string[] = []
|
|
139
|
+
for (const key of keys ?? Object.keys(ref.cases)) {
|
|
140
|
+
const a = ref.cases[key]
|
|
141
|
+
const b = cand.cases[key]
|
|
142
|
+
if (!a || !b) {
|
|
143
|
+
missingCases.push(key)
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
cases.push(compareCase(key, a, b, tol))
|
|
147
|
+
}
|
|
148
|
+
return { ref: ref.renderer, cand: cand.renderer, tolerance: tol, cases, missingCases }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Divergences of a case (matched nodes outside tolerance). */
|
|
152
|
+
export const divergences = (c: CaseReport) => c.diffs.length
|
|
153
|
+
|
|
154
|
+
const pad = (s: string | number, n: number, right = false) => (right ? String(s).padStart(n) : String(s).padEnd(n))
|
|
155
|
+
|
|
156
|
+
/** The summary table: one row per case. */
|
|
157
|
+
export function formatTable(report: Report): string {
|
|
158
|
+
const head = [pad(`case`, 44), pad(`matched`, 7, true), pad(`ok`, 5, true), pad(`size`, 5, true), pad(`pos`, 5, true), pad(`wrap`, 5, true), pad(`±1ln`, 5, true), pad(`origin`, 6, true), pad(`only ref`, 8, true), pad(`only cand`, 9, true), pad(`max Δ`, 8, true), pad(`height ref/cand`, 17, true)].join(` `)
|
|
159
|
+
const rows = report.cases.map((c) =>
|
|
160
|
+
[pad(c.key, 44), pad(c.matched, 7, true), pad(c.within, 5, true), pad(c.size, 5, true), pad(c.position, 5, true), pad(c.wrap, 5, true), pad(c.wrapTolerated, 5, true), pad(c.origins, 6, true), pad(c.onlyRef.length, 8, true), pad(c.onlyCand.length, 9, true), pad(c.maxDelta.toFixed(1), 8, true), pad(`${Math.round(c.height.ref)}/${Math.round(c.height.cand)}`, 17, true)].join(` `)
|
|
161
|
+
)
|
|
162
|
+
const t = report.cases.reduce((s, c) => ({ matched: s.matched + c.matched, within: s.within + c.within, size: s.size + c.size, position: s.position + c.position, wrap: s.wrap + c.wrap, tol: s.tol + c.wrapTolerated, origins: s.origins + c.origins, onlyRef: s.onlyRef + c.onlyRef.length, onlyCand: s.onlyCand + c.onlyCand.length }), { matched: 0, within: 0, size: 0, position: 0, wrap: 0, tol: 0, origins: 0, onlyRef: 0, onlyCand: 0 })
|
|
163
|
+
const total = [pad(`TOTAL (${report.cases.length} cases)`, 44), pad(t.matched, 7, true), pad(t.within, 5, true), pad(t.size, 5, true), pad(t.position, 5, true), pad(t.wrap, 5, true), pad(t.tol, 5, true), pad(t.origins, 6, true), pad(t.onlyRef, 8, true), pad(t.onlyCand, 9, true), pad(``, 8), pad(``, 17)].join(` `)
|
|
164
|
+
const lines = [`${report.ref} (ref) vs ${report.cand} (cand) — ±${report.tolerance.px}px, wrapped text ±${report.tolerance.textLines} line`, head, `-`.repeat(head.length), ...rows, `-`.repeat(head.length), total]
|
|
165
|
+
if (report.missingCases.length) lines.push(`missing cases: ${report.missingCases.join(`, `)}`)
|
|
166
|
+
return lines.join(`\n`)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const fmtBox = (b: NodeDiff[`ref`]) => `${b.x},${b.y} ${b.w}×${b.h}${b.lines !== undefined ? ` ${b.lines}ln@${b.lh}` : ``}`
|
|
170
|
+
|
|
171
|
+
/** One case's divergences: origins first (the list to fix), then a count of
|
|
172
|
+
* the cascade, the tolerated rewraps and the unmatched ids. */
|
|
173
|
+
export function formatCase(c: CaseReport, limit = 60): string {
|
|
174
|
+
const out = [`## ${c.key}`]
|
|
175
|
+
const origins = c.diffs.filter((d) => d.origin)
|
|
176
|
+
for (const d of origins.slice(0, limit)) out.push(` ${d.kinds.join(`+`).padEnd(13)} ${d.id} (${d.component}) ref ${fmtBox(d.ref)} → cand ${fmtBox(d.cand)} Δ ${d.d.x},${d.d.y} ${d.d.w}×${d.d.h}${d.text ? ` "${d.text.slice(0, 40)}"` : ``}`)
|
|
177
|
+
if (origins.length > limit) out.push(` … ${origins.length - limit} more origins`)
|
|
178
|
+
const cascade = c.diffs.length - origins.length
|
|
179
|
+
if (cascade) out.push(` + ${cascade} cascaded (moved/resized by the origins above)`)
|
|
180
|
+
if (c.rewraps.length) out.push(` rewrapped within tolerance: ${c.rewraps.slice(0, 12).join(`; `)}${c.rewraps.length > 12 ? ` … +${c.rewraps.length - 12}` : ``}`)
|
|
181
|
+
if (c.onlyRef.length) out.push(` only in ref (${c.onlyRef.length}): ${c.onlyRef.slice(0, 12).join(`, `)}${c.onlyRef.length > 12 ? ` …` : ``}`)
|
|
182
|
+
if (c.onlyCand.length) out.push(` only in cand (${c.onlyCand.length}): ${c.onlyCand.slice(0, 12).join(`, `)}${c.onlyCand.length > 12 ? ` …` : ``}`)
|
|
183
|
+
return out.join(`\n`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Divergences grouped ACROSS cases: the same node diverging the same way
|
|
187
|
+
* in many cases is one finding (`id kinds → case count, Δ range`). */
|
|
188
|
+
export function groupFindings(report: Report): { id: string; component: string; kinds: string; cases: number; dw: [number, number]; dh: [number, number]; text?: string; example: string }[] {
|
|
189
|
+
const groups = new Map<string, { id: string; component: string; kinds: string; cases: number; dw: [number, number]; dh: [number, number]; text?: string; example: string }>()
|
|
190
|
+
for (const c of report.cases)
|
|
191
|
+
for (const d of c.diffs) {
|
|
192
|
+
if (!d.origin) continue
|
|
193
|
+
const k = `${d.id}|${d.kinds.join(`+`)}`
|
|
194
|
+
const g = groups.get(k) ?? { id: d.id, component: d.component, kinds: d.kinds.join(`+`), cases: 0, dw: [Infinity, -Infinity] as [number, number], dh: [Infinity, -Infinity] as [number, number], text: d.text, example: `${c.key}: ref ${fmtBox(d.ref)} → cand ${fmtBox(d.cand)}` }
|
|
195
|
+
g.cases++
|
|
196
|
+
g.dw = [Math.min(g.dw[0], d.d.w), Math.max(g.dw[1], d.d.w)]
|
|
197
|
+
g.dh = [Math.min(g.dh[0], d.d.h), Math.max(g.dh[1], d.d.h)]
|
|
198
|
+
groups.set(k, g)
|
|
199
|
+
}
|
|
200
|
+
return [...groups.values()].sort((a, b) => b.cases - a.cases || a.id.localeCompare(b.id))
|
|
201
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// The WEB producer of a frame dump (conformance/dump.ts), run inside the
|
|
2
|
+
// page: every `[data-xui-id]` element of the surface's MAIN tree (overlay and
|
|
3
|
+
// toast layers excluded, `display: none` excluded) with its border box
|
|
4
|
+
// relative to the root node's (getBoundingClientRect, the browser's real
|
|
5
|
+
// fonts), minus collapsed 0×0 subtrees (dump.ts dropCollapsed). `text` = the text nodes the element owns (not a nested node's);
|
|
6
|
+
// `lines` (text components only) = the distinct line boxes of that text
|
|
7
|
+
// (Range client rects grouped by their vertical centre), `lh` = the computed
|
|
8
|
+
// line height.
|
|
9
|
+
//
|
|
10
|
+
// Round 2 (docs/round-2-contract.md §8): frames are LAYOUT boxes — the dump
|
|
11
|
+
// neutralises `transform`/`translate`/`rotate`/`scale` and animations on
|
|
12
|
+
// every node while it measures (they are paint-only by contract: a selected
|
|
13
|
+
// tab icon scaled 1.15 keeps its 20 px box), and skips what the contract does
|
|
14
|
+
// not place: nodes inside an inactive carousel page (`[data-xui-inactive]`).
|
|
15
|
+
|
|
16
|
+
import { dropCollapsed, roundNode, type CaseDump, type DumpNode } from "./dump"
|
|
17
|
+
|
|
18
|
+
/** The line boxes of a run of text nodes: client rects grouped by centre. */
|
|
19
|
+
function lineCount(texts: Text[]): number {
|
|
20
|
+
const centres: number[] = []
|
|
21
|
+
const range = document.createRange()
|
|
22
|
+
for (const t of texts) {
|
|
23
|
+
range.selectNodeContents(t)
|
|
24
|
+
for (const r of Array.from(range.getClientRects())) {
|
|
25
|
+
if (r.width === 0 && r.height === 0) continue
|
|
26
|
+
const c = r.top + r.height / 2
|
|
27
|
+
if (!centres.some((x) => Math.abs(x - c) < r.height / 2)) centres.push(c)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return centres.length
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The text nodes `el` owns (skipping nested placed nodes). */
|
|
34
|
+
function ownTexts(el: HTMLElement): Text[] {
|
|
35
|
+
const out: Text[] = []
|
|
36
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
|
|
37
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
38
|
+
const t = n as Text
|
|
39
|
+
if (!t.data.trim()) continue
|
|
40
|
+
if (t.parentElement?.closest(`[data-xui-id]`) !== el) continue
|
|
41
|
+
out.push(t)
|
|
42
|
+
}
|
|
43
|
+
return out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The rule that turns paint-only transforms and animations off while the
|
|
47
|
+
* dump measures. */
|
|
48
|
+
export const LAYOUT_ONLY_CSS = `[data-xui-id]{transform:none!important;translate:none!important;rotate:none!important;scale:none!important;animation:none!important;transition:none!important}`
|
|
49
|
+
|
|
50
|
+
export function domDump(surface: Element, textComponents: readonly string[]): CaseDump {
|
|
51
|
+
const style = document.createElement(`style`)
|
|
52
|
+
style.textContent = LAYOUT_ONLY_CSS
|
|
53
|
+
document.head.appendChild(style)
|
|
54
|
+
try {
|
|
55
|
+
return measure(surface, textComponents)
|
|
56
|
+
} finally {
|
|
57
|
+
style.remove()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function measure(surface: Element, textComponents: readonly string[]): CaseDump {
|
|
62
|
+
const rootEl = surface.querySelector<HTMLElement>(`[data-xui-id]`)
|
|
63
|
+
if (!rootEl) return { width: 0, height: 0, nodes: [] }
|
|
64
|
+
const o = rootEl.getBoundingClientRect()
|
|
65
|
+
const nodes: DumpNode[] = []
|
|
66
|
+
const seen = new Set<string>()
|
|
67
|
+
for (const el of Array.from(surface.querySelectorAll<HTMLElement>(`[data-xui-id]`))) {
|
|
68
|
+
if (el.closest(`[data-xui-layer]`)) continue
|
|
69
|
+
if (el.closest(`[data-xui-inactive]`)) continue
|
|
70
|
+
if (el.getClientRects().length === 0) continue
|
|
71
|
+
const id = el.dataset.xuiId!
|
|
72
|
+
if (seen.has(id)) continue
|
|
73
|
+
seen.add(id)
|
|
74
|
+
const r = el.getBoundingClientRect()
|
|
75
|
+
const node: DumpNode = { id, component: el.dataset.xuiC ?? ``, x: r.left - o.left, y: r.top - o.top, w: r.width, h: r.height }
|
|
76
|
+
if (el.dataset.xuiPart) node.part = el.dataset.xuiPart
|
|
77
|
+
const parent = el.parentElement?.closest<HTMLElement>(`[data-xui-id]`)
|
|
78
|
+
if (parent) node.parent = parent.dataset.xuiId
|
|
79
|
+
const texts = ownTexts(el)
|
|
80
|
+
if (texts.length) {
|
|
81
|
+
node.text = texts
|
|
82
|
+
.map((t) => t.data)
|
|
83
|
+
.join(``)
|
|
84
|
+
.replace(/\s+/g, ` `)
|
|
85
|
+
.trim()
|
|
86
|
+
.slice(0, 80)
|
|
87
|
+
if (textComponents.includes(node.component)) {
|
|
88
|
+
node.lines = lineCount(texts)
|
|
89
|
+
const lh = parseFloat(getComputedStyle(el).lineHeight)
|
|
90
|
+
if (Number.isFinite(lh)) node.lh = lh
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
nodes.push(roundNode(node))
|
|
94
|
+
}
|
|
95
|
+
return { width: Math.round(o.width * 100) / 100, height: Math.round(o.height * 100) / 100, nodes: dropCollapsed(nodes) }
|
|
96
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// The FRAME DUMP format of the renderer conformance harness: every placed
|
|
2
|
+
// node of one case (fixture × theme × mode × width × direction) as
|
|
3
|
+
// {id, component, part, parent, x, y, w, h, text?, lines?, lh?}, positions
|
|
4
|
+
// relative to the surface's root node, in CSS px. Produced by the React
|
|
5
|
+
// renderer in headless Chromium (conformance/dom.ts, real fonts) and by the
|
|
6
|
+
// gpui painter headless (apps/desktop/crates/exponential-ui-gpui/examples/
|
|
7
|
+
// conformance_dump.rs, gpui's cosmic-text system with the same font files);
|
|
8
|
+
// conformance/compare.ts diffs two of them. The case matrix and the fixtures
|
|
9
|
+
// live in fixtures/conformance-cases.json, the font set in conformance/fonts.json.
|
|
10
|
+
|
|
11
|
+
export const DUMP_FORMAT = `xui-frame-dump/1`
|
|
12
|
+
|
|
13
|
+
export interface DumpNode {
|
|
14
|
+
id: string
|
|
15
|
+
component: string
|
|
16
|
+
part?: string
|
|
17
|
+
/** The nearest placed ancestor's id. */
|
|
18
|
+
parent?: string
|
|
19
|
+
x: number
|
|
20
|
+
y: number
|
|
21
|
+
w: number
|
|
22
|
+
h: number
|
|
23
|
+
/** The text the node lays out itself (leaves; ≤ 80 chars, informational). */
|
|
24
|
+
text?: string
|
|
25
|
+
/** Laid-out text lines (text components only: `textComponents`). */
|
|
26
|
+
lines?: number
|
|
27
|
+
/** The text's line height (px). */
|
|
28
|
+
lh?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CaseDump {
|
|
32
|
+
width: number
|
|
33
|
+
height: number
|
|
34
|
+
nodes: DumpNode[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Dump {
|
|
38
|
+
format: string
|
|
39
|
+
/** `react-chromium` | `gpui-cosmic-headless` | … */
|
|
40
|
+
renderer: string
|
|
41
|
+
/** The font files the dump was laid out with. */
|
|
42
|
+
fonts: string
|
|
43
|
+
cases: Record<string, CaseDump>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ConformanceManifest {
|
|
47
|
+
format: number
|
|
48
|
+
fonts: string
|
|
49
|
+
fixtures: Record<string, { tree?: string; data?: string; geometry?: string }>
|
|
50
|
+
themes: string[]
|
|
51
|
+
modes: string[]
|
|
52
|
+
widths: number[]
|
|
53
|
+
directions: (`ltr` | `rtl`)[]
|
|
54
|
+
locale: string
|
|
55
|
+
textComponents: string[]
|
|
56
|
+
tolerance: { px: number; textLines: number }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FontFace {
|
|
60
|
+
file: string
|
|
61
|
+
weight: number
|
|
62
|
+
style: `normal` | `italic`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface FontManifest {
|
|
66
|
+
default: string
|
|
67
|
+
families: Record<string, { faces?: FontFace[]; substitute?: string }>
|
|
68
|
+
lastResort?: { families: string[] }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ConformanceCase {
|
|
72
|
+
key: string
|
|
73
|
+
fixture: string
|
|
74
|
+
theme: string
|
|
75
|
+
mode: `light` | `dark`
|
|
76
|
+
width: number
|
|
77
|
+
direction: `ltr` | `rtl`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const MANIFEST_PATH = `packages/exponential-ui/fixtures/conformance-cases.json`
|
|
81
|
+
export const BASELINE_PATH = `packages/exponential-ui/fixtures/conformance-baseline.json`
|
|
82
|
+
export const KNOWN_PATH = `packages/exponential-ui/fixtures/conformance-known.json`
|
|
83
|
+
|
|
84
|
+
/** `<fixture>/<theme>/<mode>/<width>/<direction>` (the Rust side builds the same). */
|
|
85
|
+
export const caseKey = (c: Omit<ConformanceCase, `key`>) => `${c.fixture}/${c.theme}/${c.mode}/${c.width}/${c.direction}`
|
|
86
|
+
|
|
87
|
+
export function parseCaseKey(key: string): ConformanceCase {
|
|
88
|
+
const [fixture, theme, mode, width, direction] = key.split(`/`)
|
|
89
|
+
return { key, fixture, theme, mode: mode as `light` | `dark`, width: Number(width), direction: direction as `ltr` | `rtl` }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Every case of the matrix, in the manifest's order (the Rust order too). */
|
|
93
|
+
export function allCases(m: ConformanceManifest): ConformanceCase[] {
|
|
94
|
+
const out: ConformanceCase[] = []
|
|
95
|
+
for (const fixture of Object.keys(m.fixtures))
|
|
96
|
+
for (const theme of m.themes)
|
|
97
|
+
for (const mode of m.modes)
|
|
98
|
+
for (const width of m.widths)
|
|
99
|
+
for (const direction of m.directions) {
|
|
100
|
+
const c = { fixture, theme, mode: mode as `light` | `dark`, width, direction }
|
|
101
|
+
out.push({ key: caseKey(c), ...c })
|
|
102
|
+
}
|
|
103
|
+
return out
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The case's nested tree (root `direction` = the case's) and data model;
|
|
107
|
+
* `read` loads a repo-relative JSON file (fetch in the page, fs in Bun). */
|
|
108
|
+
export async function caseInput(m: ConformanceManifest, c: ConformanceCase, read: (path: string) => Promise<unknown>): Promise<{ tree: Record<string, unknown>; data: Record<string, unknown> }> {
|
|
109
|
+
const spec = m.fixtures[c.fixture]
|
|
110
|
+
if (!spec) throw new Error(`unknown conformance fixture ${c.fixture}`)
|
|
111
|
+
let tree: Record<string, unknown>
|
|
112
|
+
let data: Record<string, unknown> = {}
|
|
113
|
+
if (spec.geometry) {
|
|
114
|
+
const g = (await read(spec.geometry)) as { surface: Record<string, unknown>; data?: Record<string, unknown> }
|
|
115
|
+
tree = structuredClone(g.surface)
|
|
116
|
+
data = structuredClone(g.data ?? {})
|
|
117
|
+
} else {
|
|
118
|
+
tree = structuredClone((await read(spec.tree!)) as Record<string, unknown>)
|
|
119
|
+
if (spec.data) {
|
|
120
|
+
const { $comment: _c, ...rest } = (await read(spec.data)) as Record<string, unknown>
|
|
121
|
+
data = rest
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
tree.style = { ...((tree.style as Record<string, unknown> | undefined) ?? {}), direction: c.direction }
|
|
125
|
+
return { tree, data }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The @font-face rules of the font set (`url(path)` → `src`). Substitutes
|
|
129
|
+
* are declared under the substituted name with the substitute's files. */
|
|
130
|
+
export function fontFaceCss(fonts: FontManifest, url: (file: string) => string): string {
|
|
131
|
+
const rules: string[] = []
|
|
132
|
+
for (const [family, spec] of Object.entries(fonts.families)) {
|
|
133
|
+
const faces = spec.faces ?? (spec.substitute ? fonts.families[spec.substitute]?.faces : undefined) ?? []
|
|
134
|
+
for (const f of faces) rules.push(`@font-face { font-family: "${family}"; src: url("${url(f.file)}") format("truetype"); font-weight: ${f.weight}; font-style: ${f.style}; font-display: block; }`)
|
|
135
|
+
}
|
|
136
|
+
return rules.join(`\n`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Every font file the set names (for the server's allowlist). */
|
|
140
|
+
export const fontFiles = (fonts: FontManifest) => Object.values(fonts.families).flatMap((f) => (f.faces ?? []).map((x) => x.file))
|
|
141
|
+
|
|
142
|
+
/** The one placement rule both producers apply: a node whose box is 0×0
|
|
143
|
+
* together with every descendant's is NOT placed (the web drops
|
|
144
|
+
* `display: none` outright, the core gives such subtrees 0×0 frames). */
|
|
145
|
+
export function dropCollapsed(nodes: DumpNode[]): DumpNode[] {
|
|
146
|
+
const parentOf = new Map(nodes.map((n) => [n.id, n.parent]))
|
|
147
|
+
const visible = new Set<string>()
|
|
148
|
+
for (const n of nodes) {
|
|
149
|
+
if (n.w <= 0 && n.h <= 0) continue
|
|
150
|
+
// The node and every ancestor are placed (any order of `nodes`).
|
|
151
|
+
for (let id: string | undefined = n.id; id && !visible.has(id); id = parentOf.get(id)) visible.add(id)
|
|
152
|
+
}
|
|
153
|
+
return nodes.filter((n) => visible.has(n.id))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const r2 = (v: number) => Math.round(v * 100) / 100
|
|
157
|
+
|
|
158
|
+
/** Round a dump's numbers to 1/100 px (both producers do). */
|
|
159
|
+
export function roundNode(n: DumpNode): DumpNode {
|
|
160
|
+
const out: DumpNode = { ...n, x: r2(n.x), y: r2(n.y), w: r2(n.w), h: r2(n.h) }
|
|
161
|
+
if (n.lh !== undefined) out.lh = r2(n.lh)
|
|
162
|
+
return out
|
|
163
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "The conformance FONT SET: every theme font family name → the font files BOTH renderers lay text out with (the web as @font-face rules named after the family, served from these files; gpui as the ONLY fonts in a cosmic-text system without system fonts). Paths are repo-relative and committed. A family without committed files names a `substitute` (its metrics are the substitute's, on both sides — the comparison stays apples to apples, it no longer tests that family). CSS generic names the themes use as a family (`ui-monospace`) are mapped too. `default` = the face the desktop gets for `.SystemUIFont` and every unmapped family. Round 2: Nunito and Fira Code (the playful theme's families, SIL OFL 1.1, licences beside the files in conformance/fonts/) are real faces now; Nunito's statics are instances of Google Fonts' variable Nunito[wght].ttf (fonttools varLib.instancer --update-name-table), Fira Code's are the v6.2 release's. Not shipped in the npm package.",
|
|
3
|
+
"default": "Inter",
|
|
4
|
+
"families": {
|
|
5
|
+
"Inter": {
|
|
6
|
+
"faces": [
|
|
7
|
+
{
|
|
8
|
+
"file": "apps/desktop/assets/fonts/Inter-Light.ttf",
|
|
9
|
+
"weight": 300,
|
|
10
|
+
"style": "normal"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"file": "apps/desktop/assets/fonts/Inter-Regular.ttf",
|
|
14
|
+
"weight": 400,
|
|
15
|
+
"style": "normal"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"file": "apps/desktop/assets/fonts/Inter-Italic.ttf",
|
|
19
|
+
"weight": 400,
|
|
20
|
+
"style": "italic"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "apps/desktop/assets/fonts/Inter-Medium.ttf",
|
|
24
|
+
"weight": 500,
|
|
25
|
+
"style": "normal"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"file": "apps/desktop/assets/fonts/Inter-SemiBold.ttf",
|
|
29
|
+
"weight": 600,
|
|
30
|
+
"style": "normal"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"file": "apps/desktop/assets/fonts/Inter-Bold.ttf",
|
|
34
|
+
"weight": 700,
|
|
35
|
+
"style": "normal"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"Geist": {
|
|
40
|
+
"faces": [
|
|
41
|
+
{
|
|
42
|
+
"file": "apps/marketing/scripts/assets/Geist-Regular.ttf",
|
|
43
|
+
"weight": 400,
|
|
44
|
+
"style": "normal"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"file": "apps/marketing/scripts/assets/Geist-SemiBold.ttf",
|
|
48
|
+
"weight": 600,
|
|
49
|
+
"style": "normal"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"file": "apps/marketing/scripts/assets/Geist-Bold.ttf",
|
|
53
|
+
"weight": 700,
|
|
54
|
+
"style": "normal"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"JetBrains Mono": {
|
|
59
|
+
"faces": [
|
|
60
|
+
{
|
|
61
|
+
"file": "apps/desktop/assets/fonts/JetBrainsMono-Regular.ttf",
|
|
62
|
+
"weight": 400,
|
|
63
|
+
"style": "normal"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"file": "apps/desktop/assets/fonts/JetBrainsMono-Italic.ttf",
|
|
67
|
+
"weight": 400,
|
|
68
|
+
"style": "italic"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"file": "apps/desktop/assets/fonts/JetBrainsMono-Bold.ttf",
|
|
72
|
+
"weight": 700,
|
|
73
|
+
"style": "normal"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"Geist Mono": {
|
|
78
|
+
"substitute": "JetBrains Mono"
|
|
79
|
+
},
|
|
80
|
+
"ui-monospace": {
|
|
81
|
+
"substitute": "JetBrains Mono"
|
|
82
|
+
},
|
|
83
|
+
"Nunito": {
|
|
84
|
+
"faces": [
|
|
85
|
+
{
|
|
86
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-Light.ttf",
|
|
87
|
+
"weight": 300,
|
|
88
|
+
"style": "normal"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-Regular.ttf",
|
|
92
|
+
"weight": 400,
|
|
93
|
+
"style": "normal"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-Italic.ttf",
|
|
97
|
+
"weight": 400,
|
|
98
|
+
"style": "italic"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-Medium.ttf",
|
|
102
|
+
"weight": 500,
|
|
103
|
+
"style": "normal"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-SemiBold.ttf",
|
|
107
|
+
"weight": 600,
|
|
108
|
+
"style": "normal"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-Bold.ttf",
|
|
112
|
+
"weight": 700,
|
|
113
|
+
"style": "normal"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"file": "packages/exponential-ui/conformance/fonts/Nunito-ExtraBold.ttf",
|
|
117
|
+
"weight": 800,
|
|
118
|
+
"style": "normal"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"Fira Code": {
|
|
123
|
+
"faces": [
|
|
124
|
+
{
|
|
125
|
+
"file": "packages/exponential-ui/conformance/fonts/FiraCode-Regular.ttf",
|
|
126
|
+
"weight": 400,
|
|
127
|
+
"style": "normal"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"file": "packages/exponential-ui/conformance/fonts/FiraCode-Medium.ttf",
|
|
131
|
+
"weight": 500,
|
|
132
|
+
"style": "normal"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"file": "packages/exponential-ui/conformance/fonts/FiraCode-SemiBold.ttf",
|
|
136
|
+
"weight": 600,
|
|
137
|
+
"style": "normal"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"file": "packages/exponential-ui/conformance/fonts/FiraCode-Bold.ttf",
|
|
141
|
+
"weight": 700,
|
|
142
|
+
"style": "normal"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"lastResort": {
|
|
148
|
+
"$comment": "gpui's TextSystem panics when a probed family AND its fixed fallback stack (IBM Plex Sans, Lilex, Helvetica, Segoe UI, Ubuntu, Adwaita Sans, Cantarell, Noto Sans, DejaVu Sans, Arial) all fail to resolve; the painter probes `SF Mono`, `Menlo`… for generic mono names. The desktop dump therefore also loads the FIRST of these system families fontconfig finds (never used for a mapped family; cosmic-text may use it for glyphs the set lacks, as Chromium falls back to system fonts).",
|
|
149
|
+
"families": [
|
|
150
|
+
"DejaVu Sans",
|
|
151
|
+
"Noto Sans"
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|