@astryxdesign/cli 0.6.5-canary.01972bc → 0.6.5-canary.01fb22f
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/README.md +2 -2
- package/api/build/_adapter.d.mts +14 -0
- package/api/build/_adapter.mjs +58 -1
- package/api/build/build.test.mjs +74 -1
- package/api/build/kit/kit.mjs +39 -7
- package/api/build/kit/rank.d.mts +8 -0
- package/api/build/kit/rank.mjs +14 -0
- package/api/build/kit/weights.d.mts +82 -0
- package/api/build/kit/weights.json +1 -0
- package/api/build/kit/weights.mjs +305 -0
- package/api/build/kit/weights.test.mjs +190 -0
- package/api/docs/docs.d.mts +6 -0
- package/api/docs/docs.depth.test.mjs +132 -0
- package/api/docs/docs.doc.mjs +31 -2
- package/api/docs/docs.mjs +44 -1
- package/api/docs/docs.type.d.mts +38 -1
- package/api/docs/docs.type.mjs +18 -1
- package/api/docs/node/node.d.mts +28 -3
- package/api/docs/node/node.mjs +146 -23
- package/api/gap-report/gap-report.d.mts +3 -1
- package/api/gap-report/gap-report.mjs +12 -2
- package/api/gap-report/gap-report.test.mjs +106 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/integration/add-theme.mjs +93 -3
- package/api/integration/add-theme.test.mjs +100 -0
- package/api/integration/integrationAddTheme.doc.mjs +1 -1
- package/api/search/search.d.mts +12 -5
- package/api/search/search.doc.mjs +4 -4
- package/api/search/search.mjs +123 -53
- package/api/search/search.test.mjs +123 -8
- package/api/search/search.type.d.mts +5 -5
- package/api/search/search.type.mjs +5 -5
- package/api/theme/build/build.icon-lineage.test.mjs +117 -0
- package/api/theme/build/build.icon-preservation.test.mjs +639 -0
- package/api/theme/build/build.mjs +146 -69
- package/api/theme/build/build.test.mjs +190 -1
- package/api/theme/build/icon-imports.d.mts +47 -0
- package/api/theme/build/icon-imports.mjs +691 -0
- package/api/theme/themeBuild.doc.d.mts +4 -1
- package/api/theme/themeBuild.doc.mjs +14 -3
- package/assets/docs/theme.doc.mjs +2 -1
- package/assets/docs/tokens.doc.mjs +3 -3
- package/assets/docs/tree/add-a-theme.doc.mjs +4 -0
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupShowcase.tsx +38 -0
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupWithSelectable.doc.mjs +20 -0
- package/assets/templates/blocks/components/DropdownMenuGroup/DropdownMenuGroupWithSelectable.tsx +48 -0
- package/clients/cli/commands/build-theme.mjs +8 -2
- package/clients/cli/commands/discover.mjs +29 -1
- package/clients/cli/commands/discover.no-source.test.mjs +75 -0
- package/clients/cli/commands/docs.depth.test.mjs +219 -0
- package/clients/cli/commands/docs.doc.mjs +15 -2
- package/clients/cli/commands/docs.mjs +222 -5
- package/clients/cli/commands/gap-report.mjs +12 -0
- package/clients/cli/commands/gap-report.test.mjs +63 -0
- package/clients/cli/commands/integration-add.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +5 -4
- package/clients/cli/commands/search.mjs +30 -6
- package/clients/cli/commands/search.test.mjs +60 -16
- package/clients/cli/commands/theme-list.behavior.test.mjs +41 -0
- package/foundation/agent-docs/agent-docs.mjs +2 -1
- package/foundation/agent-docs/agent-docs.test.mjs +8 -0
- package/foundation/discovery/theme-discovery.d.mts +6 -0
- package/foundation/discovery/theme-discovery.mjs +9 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Results for "button" (20 of 239):
|
|
|
69
69
|
|
|
70
70
|
Options:
|
|
71
71
|
|
|
72
|
-
- `--type <component|hook|doc|template>`: restrict to a single domain
|
|
72
|
+
- `--type <component|hook|doc|template|theme>`: restrict to a single domain (`doc` and `theme` work outside an app too)
|
|
73
73
|
- `--limit <n>`: cap the number of results (default 20)
|
|
74
74
|
- `--verbose`: also print each result's match score and reason
|
|
75
75
|
- `--json`: typed `{ apiVersion, type: 'search', data: { query, matchCount, results } }` envelope — `matchCount` is how many candidates matched in total, `results` the slice `--limit` allowed
|
|
@@ -91,7 +91,7 @@ Options:
|
|
|
91
91
|
| `init` | Initialize the design system in your project |
|
|
92
92
|
| `integration` | Author and verify an Astryx integration package |
|
|
93
93
|
| `layout` | Generate XDS layouts from compressed expressions (XLE/XLO) |
|
|
94
|
-
| `search` | Search components, hooks, docs, and
|
|
94
|
+
| `search` | Search components, hooks, docs, templates, and themes in one ranked list |
|
|
95
95
|
| `swizzle` | Copy component source for customization |
|
|
96
96
|
| `template` | List, show, or scaffold page and block templates |
|
|
97
97
|
| `theme` | Create and build themes: add a shipped one, compile to CSS, or list what a theme can override |
|
package/api/build/_adapter.d.mts
CHANGED
|
@@ -40,6 +40,20 @@ export function loadPageTemplates(cwd: string): Promise<PageTemplate[]>;
|
|
|
40
40
|
* @returns {Promise<ComponentWords[]>}
|
|
41
41
|
*/
|
|
42
42
|
export function loadComponents(cwd: string): Promise<ComponentWords[]>;
|
|
43
|
+
/**
|
|
44
|
+
* The matcher weights checked in beside the kit (`kit/weights.json`), read
|
|
45
|
+
* once; null when the file is absent or unreadable.
|
|
46
|
+
* @returns {import('./kit/weights.mjs').WeightsFile | null}
|
|
47
|
+
*/
|
|
48
|
+
export function loadWeights(): import("./kit/weights.mjs").WeightsFile | null;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a parsed weights file has the shape the kit reads: every row has a
|
|
51
|
+
* weight per candidate, every bias a number per candidate, and three blend
|
|
52
|
+
* numbers per member (the tables plus the ranker) and one for the shell.
|
|
53
|
+
* @param {any} file
|
|
54
|
+
* @returns {file is import('./kit/weights.mjs').WeightsFile}
|
|
55
|
+
*/
|
|
56
|
+
export function isWeightsFile(file: any): file is import("./kit/weights.mjs").WeightsFile;
|
|
43
57
|
/**
|
|
44
58
|
* A page template the kit can recommend starting from.
|
|
45
59
|
*/
|
package/api/build/_adapter.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file The build subject's environment access: the page templates a project
|
|
5
|
-
* can scaffold,
|
|
5
|
+
* can scaffold, the components it can use, and the checked-in matcher weights.
|
|
6
6
|
*
|
|
7
7
|
* @input Template and component discovery for `cwd` — the CLI's own templates
|
|
8
8
|
* and Core's components, plus any that the project's configured integrations
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* from the template subject's, components from search's.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
|
|
19
21
|
import {discoverTemplates} from '../template/template.mjs';
|
|
20
22
|
import {componentKeywords} from '../search/search.mjs';
|
|
21
23
|
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
@@ -89,3 +91,58 @@ export async function loadComponents(cwd) {
|
|
|
89
91
|
return [];
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
|
|
95
|
+
/** @type {import('./kit/weights.mjs').WeightsFile | null | undefined} */
|
|
96
|
+
let weights;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The matcher weights checked in beside the kit (`kit/weights.json`), read
|
|
100
|
+
* once; null when the file is absent or unreadable.
|
|
101
|
+
* @returns {import('./kit/weights.mjs').WeightsFile | null}
|
|
102
|
+
*/
|
|
103
|
+
export function loadWeights() {
|
|
104
|
+
if (weights === undefined) {
|
|
105
|
+
try {
|
|
106
|
+
const file = JSON.parse(
|
|
107
|
+
fs.readFileSync(new URL('./kit/weights.json', import.meta.url), 'utf8'),
|
|
108
|
+
);
|
|
109
|
+
weights = isWeightsFile(file) ? file : null;
|
|
110
|
+
} catch {
|
|
111
|
+
weights = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return weights ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether a parsed weights file has the shape the kit reads: every row has a
|
|
119
|
+
* weight per candidate, every bias a number per candidate, and three blend
|
|
120
|
+
* numbers per member (the tables plus the ranker) and one for the shell.
|
|
121
|
+
* @param {any} file
|
|
122
|
+
* @returns {file is import('./kit/weights.mjs').WeightsFile}
|
|
123
|
+
*/
|
|
124
|
+
export function isWeightsFile(file) {
|
|
125
|
+
const n = Array.isArray(file?.candidates) ? file.candidates.length : 0;
|
|
126
|
+
return (
|
|
127
|
+
n > 0 &&
|
|
128
|
+
Array.isArray(file.tables) &&
|
|
129
|
+
file.tables.length > 0 &&
|
|
130
|
+
file.tables.every(
|
|
131
|
+
(/** @type {any} */ t) =>
|
|
132
|
+
Array.isArray(t?.words) &&
|
|
133
|
+
Array.isArray(t.rows) &&
|
|
134
|
+
t.rows.length === t.words.length &&
|
|
135
|
+
t.rows.every(
|
|
136
|
+
(/** @type {any} */ r) => typeof r === 'string' && r.length === n,
|
|
137
|
+
) &&
|
|
138
|
+
Array.isArray(t.bias) &&
|
|
139
|
+
t.bias.length === n &&
|
|
140
|
+
t.bias.every((/** @type {any} */ b) => Number.isFinite(b)) &&
|
|
141
|
+
Number.isFinite(t.clip) &&
|
|
142
|
+
Number.isFinite(t.step),
|
|
143
|
+
) &&
|
|
144
|
+
Array.isArray(file.blend) &&
|
|
145
|
+
file.blend.length === 3 * (file.tables.length + 1) + 1 &&
|
|
146
|
+
file.blend.every((/** @type {any} */ x) => Number.isFinite(x))
|
|
147
|
+
);
|
|
148
|
+
}
|
package/api/build/build.test.mjs
CHANGED
|
@@ -269,7 +269,7 @@ describe('build kit — a thin kit says what to try next', () => {
|
|
|
269
269
|
// A skeleton is a 35-line excerpt: a reader who studies it and composes
|
|
270
270
|
// the rest loses the spacing the template exists to carry. A loose match
|
|
271
271
|
// is still the best start there is, so `start` scaffolds it.
|
|
272
|
-
const r = await build('
|
|
272
|
+
const r = await build('weekly business review with targets', {cwd: REPO});
|
|
273
273
|
expect(r.type).toBe('build.kit');
|
|
274
274
|
if (r.type !== 'build.kit') return;
|
|
275
275
|
expect(r.data.directMatch).toBe(false);
|
|
@@ -477,6 +477,79 @@ describe('build kit — every page starts from a template', () => {
|
|
|
477
477
|
expect(r.data.pages.map(p => p.name)).not.toContain('side-gallery');
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
+
it('names a direct match the start does not use, and calls the start direct only when it is', async () => {
|
|
481
|
+
for (const idea of ['a login form', 'a docs site for our API', 'contact form']) {
|
|
482
|
+
const r = await build(idea, {cwd: REPO});
|
|
483
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
484
|
+
expect(r.data.directMatch).toBe(true);
|
|
485
|
+
const match = r.data.pages[0].name;
|
|
486
|
+
if (r.data.start?.name === match) {
|
|
487
|
+
expect(r.data.start?.basis).toBe('direct');
|
|
488
|
+
} else {
|
|
489
|
+
expect(['closest', 'fallback']).toContain(r.data.start?.basis);
|
|
490
|
+
expect(r.data.start?.reason).toContain(`\`${match}\``);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('keeps a template search matched directly rather than the app shell', async () => {
|
|
496
|
+
for (const [idea, name] of [
|
|
497
|
+
['a login screen', 'login'],
|
|
498
|
+
['a checkout wizard', 'checkout-wizard'],
|
|
499
|
+
]) {
|
|
500
|
+
const r = await build(idea, {cwd: REPO});
|
|
501
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
502
|
+
expect(r.data.directMatch).toBe(true);
|
|
503
|
+
expect(r.data.start?.name).toBe(name);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('lets the weights choose another template over a direct match', async () => {
|
|
508
|
+
const r = await build('a login form', {cwd: REPO});
|
|
509
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
510
|
+
expect(r.data.directMatch).toBe(true);
|
|
511
|
+
const match = r.data.pages[0].name;
|
|
512
|
+
expect(r.data.start?.name).not.toBe('shell-top-nav');
|
|
513
|
+
expect(r.data.start?.name).not.toBe(match);
|
|
514
|
+
expect(r.data.start?.reason).toContain(`\`${match}\``);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('starts a part that names no page from the app shell', async () => {
|
|
518
|
+
for (const idea of ['a kanban card', 'a date range picker']) {
|
|
519
|
+
const r = await build(idea, {cwd: REPO});
|
|
520
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
521
|
+
expect(r.data.start).toMatchObject({name: 'shell-top-nav', basis: 'fallback'});
|
|
522
|
+
expect(r.data.start?.reason).toMatch(/part of a page/);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('does not keep a loose match over the app shell', async () => {
|
|
527
|
+
// Search matches no template directly, so the ranker's closest page does
|
|
528
|
+
// not override the shell the weights choose.
|
|
529
|
+
const r = await build('quarterly business review', {cwd: REPO});
|
|
530
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
531
|
+
expect(r.data.directMatch).toBe(false);
|
|
532
|
+
expect(r.data.start).toMatchObject({name: 'shell-top-nav', basis: 'fallback'});
|
|
533
|
+
expect(r.data.start?.reason).toMatch(/closest/);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('starts a new page with no matching template from the app shell', async () => {
|
|
537
|
+
const r = await build('a new page', {cwd: REPO});
|
|
538
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
539
|
+
expect(r.data.start?.name).toBe('shell-top-nav');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('starts a page the words describe from its template', async () => {
|
|
543
|
+
for (const [idea, name] of [
|
|
544
|
+
['a weekly report of sales by region', 'dashboard-scorecard'],
|
|
545
|
+
['a pricing page with three plans and a comparison table', 'table-page'],
|
|
546
|
+
]) {
|
|
547
|
+
const r = await build(idea, {cwd: REPO});
|
|
548
|
+
if (r.type !== 'build.kit') throw new Error(r.type);
|
|
549
|
+
expect(r.data.start?.name).toBe(name);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
480
553
|
it('starts a component in a container from a template with that frame', async () => {
|
|
481
554
|
// "in a modal": the modal is the frame, so the dialog template leads
|
|
482
555
|
// instead of the app shell.
|
package/api/build/kit/kit.mjs
CHANGED
|
@@ -32,8 +32,15 @@ import {findCoreDir} from '../../../foundation/fs/paths.mjs';
|
|
|
32
32
|
import {AstryxError} from '../../error.mjs';
|
|
33
33
|
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
34
34
|
import {getResultCoverage} from '../../search/coverage.mjs';
|
|
35
|
-
import {loadComponents, loadPageTemplates} from '../_adapter.mjs';
|
|
36
|
-
import {
|
|
35
|
+
import {loadComponents, loadPageTemplates, loadWeights} from '../_adapter.mjs';
|
|
36
|
+
import {
|
|
37
|
+
asksForNewPage,
|
|
38
|
+
ideaKind,
|
|
39
|
+
pickAlternatives,
|
|
40
|
+
pickStart,
|
|
41
|
+
rankPages,
|
|
42
|
+
} from './rank.mjs';
|
|
43
|
+
import {weighStart} from './weights.mjs';
|
|
37
44
|
|
|
38
45
|
/** A page at/above this score is a confident direct match. */
|
|
39
46
|
const PAGE_DIRECT = 95;
|
|
@@ -151,9 +158,10 @@ function placement(kind, inPage) {
|
|
|
151
158
|
* @param {SearchResultEntry[]} pages
|
|
152
159
|
* @param {boolean} directMatch
|
|
153
160
|
* @param {PageTemplate[]} catalog
|
|
161
|
+
* @param {string} idea
|
|
154
162
|
* @returns {Omit<BuildStart, 'alternatives'> | null}
|
|
155
163
|
*/
|
|
156
|
-
function chooseStart(ranked, kind, pages, directMatch, catalog) {
|
|
164
|
+
function chooseStart(ranked, kind, pages, directMatch, catalog, idea) {
|
|
157
165
|
const direct = directMatch ? pages[0].name : null;
|
|
158
166
|
const unready =
|
|
159
167
|
direct && !catalog.some(t => t.name === direct) ? direct : null;
|
|
@@ -170,7 +178,27 @@ function chooseStart(ranked, kind, pages, directMatch, catalog) {
|
|
|
170
178
|
direct && direct !== startName
|
|
171
179
|
? `Search matched \`${direct}\` by name, but ${place}`
|
|
172
180
|
: place[0].toUpperCase() + place.slice(1);
|
|
173
|
-
const
|
|
181
|
+
const proposed = pickStart(ranked, kind);
|
|
182
|
+
// The checked-in word weights (weights.mjs), blended with the ranker's
|
|
183
|
+
// scores, decide the start of a whole page. A part or an edit starts where
|
|
184
|
+
// the ranker's placement rules put it (spec:AST-048/FR3).
|
|
185
|
+
const weighed =
|
|
186
|
+
kind === 'page'
|
|
187
|
+
? weighStart(idea, ranked, proposed, catalog, {
|
|
188
|
+
weights: loadWeights(),
|
|
189
|
+
newPage: asksForNewPage(idea, catalog),
|
|
190
|
+
})
|
|
191
|
+
: undefined;
|
|
192
|
+
// A shell start keeps the shell the ranker named, if any, and never replaces
|
|
193
|
+
// the template the ranker chose for a page search matched directly.
|
|
194
|
+
const pick =
|
|
195
|
+
weighed === undefined
|
|
196
|
+
? proposed
|
|
197
|
+
: weighed === null
|
|
198
|
+
? proposed?.family === 'Shell' || (proposed && direct && !unready)
|
|
199
|
+
? proposed
|
|
200
|
+
: null
|
|
201
|
+
: (ranked.find(r => r.name === weighed) ?? proposed);
|
|
174
202
|
const closest = pick && catalog.find(t => t.name === pick.name);
|
|
175
203
|
if (pick && closest) {
|
|
176
204
|
const agrees = closest.name === direct;
|
|
@@ -194,7 +222,9 @@ function chooseStart(ranked, kind, pages, directMatch, catalog) {
|
|
|
194
222
|
if (shell) {
|
|
195
223
|
// The shell can also be the ranker's best guess without the evidence to
|
|
196
224
|
// lead ("horizontal site navigation"); say so rather than "no match".
|
|
197
|
-
const nearest =
|
|
225
|
+
const nearest =
|
|
226
|
+
(ranked[0]?.name === shell.name && ranked[0].hits > 0) ||
|
|
227
|
+
(weighed === null && !!proposed && proposed.family !== 'Shell');
|
|
198
228
|
const place = placement(kind, false);
|
|
199
229
|
return {
|
|
200
230
|
...asTemplate(shell),
|
|
@@ -204,7 +234,9 @@ function chooseStart(ranked, kind, pages, directMatch, catalog) {
|
|
|
204
234
|
: place
|
|
205
235
|
? placed(place, shell.name)
|
|
206
236
|
: direct
|
|
207
|
-
?
|
|
237
|
+
? weighed === null
|
|
238
|
+
? `Search matched \`${direct}\` by name, but the app shell is the closer start.`
|
|
239
|
+
: `Search matched \`${direct}\` by name, but too little of the idea fits it, so start from the app shell.`
|
|
208
240
|
: nearest
|
|
209
241
|
? 'No template is a clear match; the app shell is the closest.'
|
|
210
242
|
: loose
|
|
@@ -346,7 +378,7 @@ export async function buildKit(query, options = {}) {
|
|
|
346
378
|
)
|
|
347
379
|
: 'page';
|
|
348
380
|
const chosen = wantsPages
|
|
349
|
-
? chooseStart(ranked, kind, matchedPages, directMatch, catalog)
|
|
381
|
+
? chooseStart(ranked, kind, matchedPages, directMatch, catalog, query)
|
|
350
382
|
: null;
|
|
351
383
|
// Name the ranker's next two templates beside the start: the reader judges
|
|
352
384
|
// meaning better than keywords do, and an acceptable template is in these
|
package/api/build/kit/rank.d.mts
CHANGED
|
@@ -21,6 +21,14 @@ export function rankPages(query: string, pages: PageTemplate[]): RankedPage[];
|
|
|
21
21
|
* @returns {IdeaKind}
|
|
22
22
|
*/
|
|
23
23
|
export function ideaKind(query: string, pages: PageTemplate[], components: ComponentWords[]): IdeaKind;
|
|
24
|
+
/**
|
|
25
|
+
* Whether an idea asks for a new page (see `asksNewPage`), from the idea and
|
|
26
|
+
* the project's page templates.
|
|
27
|
+
* @param {string} query
|
|
28
|
+
* @param {PageTemplate[]} pages
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
export function asksForNewPage(query: string, pages: PageTemplate[]): boolean;
|
|
24
32
|
/**
|
|
25
33
|
* The next closest templates after the start, best first: the ones a reader
|
|
26
34
|
* should check the idea against when the start's shape is wrong. Each matched
|
package/api/build/kit/rank.mjs
CHANGED
|
@@ -543,6 +543,20 @@ function asksNewPage(phrases, familyWords) {
|
|
|
543
543
|
});
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Whether an idea asks for a new page (see `asksNewPage`), from the idea and
|
|
548
|
+
* the project's page templates.
|
|
549
|
+
* @param {string} query
|
|
550
|
+
* @param {PageTemplate[]} pages
|
|
551
|
+
* @returns {boolean}
|
|
552
|
+
*/
|
|
553
|
+
export function asksForNewPage(query, pages) {
|
|
554
|
+
return asksNewPage(
|
|
555
|
+
String(query).toLowerCase().split(PHRASE_END),
|
|
556
|
+
familyWordsOf(pages),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
546
560
|
/**
|
|
547
561
|
* The words of a component's name: "DateRangeInput" is date, range, input.
|
|
548
562
|
* @param {ComponentWords} component
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The Porter stem of a lowercase word.
|
|
6
|
+
* @param {string} word
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function stem(word: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Lowercase words of an idea that can carry weight: letters only, two or more.
|
|
12
|
+
* @param {string} idea
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
export function weightWords(idea: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Where an idea starts, by the blended weights.
|
|
18
|
+
* @param {string} idea
|
|
19
|
+
* @param {import('./rank.mjs').RankedPage[]} ranked the ranker's output for the idea
|
|
20
|
+
* @param {import('./rank.mjs').RankedPage | null} pick the ranker's own start
|
|
21
|
+
* @param {{name: string}[]} catalog the project's ready page templates
|
|
22
|
+
* @param {{weights?: WeightsFile | null, newPage?: boolean}} [options]
|
|
23
|
+
* `newPage`: the idea asks for a new page (rank.mjs `asksForNewPage`)
|
|
24
|
+
* @returns {string | null | undefined} a template id, null for the app shell,
|
|
25
|
+
* undefined without weights
|
|
26
|
+
*/
|
|
27
|
+
export function weighStart(idea: string, ranked: import("./rank.mjs").RankedPage[], pick: import("./rank.mjs").RankedPage | null, catalog: {
|
|
28
|
+
name: string;
|
|
29
|
+
}[], { weights, newPage }?: {
|
|
30
|
+
weights?: WeightsFile | null;
|
|
31
|
+
newPage?: boolean;
|
|
32
|
+
}): string | null | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* @file Word weights for `build`'s START (spec:AST-048): a checked-in table of
|
|
35
|
+
* per-template word weights, blended with the page ranker's own scores, picks
|
|
36
|
+
* the template an idea starts from.
|
|
37
|
+
*
|
|
38
|
+
* @input An idea, the ranker's output for it (`rankPages`, `pickStart`), the
|
|
39
|
+
* project's ready page templates, and the weights (`weights.json` beside this
|
|
40
|
+
* file, read by the adapter): the candidates (the app shell as `SHELL`, then page template ids), one or more
|
|
41
|
+
* word tables (stemmed words, a bias per candidate, and one row of weights
|
|
42
|
+
* per word, a character per candidate) and the blend numbers.
|
|
43
|
+
* @output `weighStart(idea, ranked, pick, catalog)`: the template id to start
|
|
44
|
+
* from, null for the app shell, or undefined when no table is available, in
|
|
45
|
+
* which case the ranker's own pick stands.
|
|
46
|
+
* @position Beside rank.mjs (api/build/kit/); kit.mjs calls it for every
|
|
47
|
+
* whole page (a part or an edit follows the ranker's placement rules). The
|
|
48
|
+
* ranker's pick of a template the tables do not list stands, so a new
|
|
49
|
+
* template can start a build.
|
|
50
|
+
*/
|
|
51
|
+
/** The app shell, as one candidate. */
|
|
52
|
+
export const SHELL: "SHELL";
|
|
53
|
+
export type WeightTable = {
|
|
54
|
+
/**
|
|
55
|
+
* stemmed words
|
|
56
|
+
*/
|
|
57
|
+
words: string[];
|
|
58
|
+
/**
|
|
59
|
+
* one per candidate
|
|
60
|
+
*/
|
|
61
|
+
bias: number[];
|
|
62
|
+
clip: number;
|
|
63
|
+
step: number;
|
|
64
|
+
/**
|
|
65
|
+
* one per word: a character per candidate,
|
|
66
|
+
* weight = (charCode - 97) * step - clip
|
|
67
|
+
*/
|
|
68
|
+
rows: string[];
|
|
69
|
+
};
|
|
70
|
+
export type WeightsFile = {
|
|
71
|
+
version: number;
|
|
72
|
+
/**
|
|
73
|
+
* `SHELL`, then page template ids
|
|
74
|
+
*/
|
|
75
|
+
candidates: string[];
|
|
76
|
+
tables: WeightTable[];
|
|
77
|
+
/**
|
|
78
|
+
* three numbers per member (the tables, in order,
|
|
79
|
+
* with the ranker second), then one for `SHELL`
|
|
80
|
+
*/
|
|
81
|
+
blend: number[];
|
|
82
|
+
};
|