@trypattern/sdk 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/bookings.d.ts +1 -7
  2. package/dist/bookingsStore.d.ts +1 -7
  3. package/dist/cli/app.js +71 -2
  4. package/dist/cli/guidance.d.ts +8 -0
  5. package/dist/cli/guidance.js +121 -0
  6. package/dist/cli/instructions.d.ts +21 -0
  7. package/dist/cli/instructions.js +206 -0
  8. package/dist/components/Calendar.d.ts +3 -1
  9. package/dist/components/Calendar.js +5 -5
  10. package/dist/components/Checkbox.js +2 -2
  11. package/dist/components/Dialog.js +10 -1
  12. package/dist/components/ScrollArea.js +4 -2
  13. package/dist/components/Select.d.ts +2 -1
  14. package/dist/components/Select.js +14 -3
  15. package/dist/components/calendarDates.d.ts +4 -3
  16. package/dist/components/calendarDates.js +8 -7
  17. package/dist/inquiriesStore.d.ts +1 -7
  18. package/dist/publishWire.d.ts +37 -59
  19. package/dist/publishWire.js +4 -2
  20. package/dist/requests.d.ts +14 -32
  21. package/dist/requests.js +12 -5
  22. package/dist/rules/lintPlugin.js +4 -1
  23. package/dist/server/index.d.ts +2 -0
  24. package/dist/server/index.js +1 -0
  25. package/dist/server/inquiries.d.ts +7 -2
  26. package/dist/server/inquiries.js +2 -2
  27. package/dist/server/requestSources.d.ts +41 -0
  28. package/dist/server/requestSources.js +157 -0
  29. package/dist/server/requests.d.ts +7 -3
  30. package/dist/server/requests.js +130 -62
  31. package/dist/start.js +14 -0
  32. package/dist/theme/tokens.stylex.d.ts +1 -0
  33. package/dist/theme/tokens.stylex.js +2 -1
  34. package/docs/CHANGELOG.json +331 -0
  35. package/docs/PATTERN.md +87 -0
  36. package/docs/app.md +74 -0
  37. package/docs/data.md +109 -0
  38. package/docs/history/agents.json +1141 -0
  39. package/docs/look.md +147 -0
  40. package/docs/requests.md +115 -0
  41. package/docs/state.md +142 -0
  42. package/package.json +4 -3
@@ -88,13 +88,7 @@ export declare const bookingTables: {
88
88
  readonly columns: {
89
89
  readonly source: z.ZodString;
90
90
  readonly requestId: z.ZodString;
91
- readonly kind: z.ZodEnum<{
92
- done: "done";
93
- accepted: "accepted";
94
- declined: "declined";
95
- replied: "replied";
96
- received: "received";
97
- }>;
91
+ readonly kind: z.ZodString;
98
92
  };
99
93
  readonly access: {
100
94
  readonly read: "server";
@@ -305,13 +305,7 @@ export declare const bookings: {
305
305
  readonly columns: {
306
306
  readonly source: z.ZodString;
307
307
  readonly requestId: z.ZodString;
308
- readonly kind: z.ZodEnum<{
309
- done: "done";
310
- accepted: "accepted";
311
- declined: "declined";
312
- replied: "replied";
313
- received: "received";
314
- }>;
308
+ readonly kind: z.ZodString;
315
309
  };
316
310
  readonly access: {
317
311
  readonly read: "server";
package/dist/cli/app.js CHANGED
@@ -26,6 +26,8 @@ import { REQUESTS_USAGE, requests } from "./requests.js";
26
26
  import { withStartingValues } from "./startingValues.js";
27
27
  import { toolBin } from "./tools.js";
28
28
  import { isNewer, latestPattern, newerPattern, updateNote } from "./updates.js";
29
+ import { REPORT_PATH, changelogIn, pendingUpdate, recordUpdate } from "./guidance.js";
30
+ import { PROPOSAL_PATH, instructionsNote, instructionsState, migrateInstructions } from "./instructions.js";
29
31
  function dependsOnPattern(directory) {
30
32
  try {
31
33
  const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
@@ -84,6 +86,9 @@ const SCREENSHOT_WORKER = join(SDK_SOURCE, 'cli', `screenshot${SDK_EXTENSION}`);
84
86
  const VISUAL_URL_ENV = 'PATTERN_VISUAL_URL';
85
87
  const GLOBAL_FLAGS = ['--json', '--help', '-h'];
86
88
  const NUDGED_COMMANDS = new Set(['start', 'check', 'publish', 'data']);
89
+ const GUIDED_COMMANDS = new Set(['start', 'check']);
90
+ const SDK_PACKAGE = resolve(SDK_SOURCE, '..');
91
+ const INSTALLED_SDK = join(ROOT, 'node_modules', '@trypattern', 'sdk');
87
92
  const VALUE_FLAGS = new Set([
88
93
  '--commands',
89
94
  '--name',
@@ -403,6 +408,16 @@ progress). New Pattern features on the live site, such as the owner approving ap
403
408
  need that publish.`,
404
409
  run: update
405
410
  },
411
+ 'migrate-instructions': {
412
+ summary: "Move an older AGENTS.md onto Pattern's guide, keeping what's about this site",
413
+ details: `Pattern's rules now ship inside Pattern (node_modules/@trypattern/sdk/docs/PATTERN.md), and
414
+ AGENTS.md imports them. This removes the sections of AGENTS.md that are Pattern's old template text,
415
+ unchanged, and keeps everything else. A section that mixes Pattern's rules with this project's files,
416
+ or one of Pattern's sections changed for this project, stays in AGENTS.md with a proposed replacement
417
+ in ${PROPOSAL_PATH}; decide each one, then delete that file. app check and app start say
418
+ "instructions migration incomplete" until it's done. Does nothing once AGENTS.md imports the guide.`,
419
+ run: instructions
420
+ },
406
421
  versions: {
407
422
  summary: 'List the versions that went live, newest first',
408
423
  details: 'Each version with its number, whether it went live, when, and who published it. app restore <number> puts one back.',
@@ -1637,12 +1652,56 @@ async function update() {
1637
1652
  text: `✗ pnpm couldn’t install Pattern ${latest}:\n${installed.output.trim().split('\n').slice(-LOG_TAIL_LINES).join('\n')}`
1638
1653
  };
1639
1654
  }
1655
+ const report = recordUpdate(ROOT, changelogIn(INSTALLED_SDK), current, latest);
1656
+ const instructions = instructionsNote(instructionsState(ROOT));
1640
1657
  return {
1641
1658
  code: 0,
1642
- data: { current, latest, updated: true },
1643
- text: `✓ Updated Pattern ${current} → ${latest}\n next run app migration pattern_${latest.replaceAll('.', '_')} (it writes nothing when Pattern's own tables didn't change), then app check, then app publish --message 'Updated Pattern' (straight away if nothing else is in progress) so the live site gets it`
1659
+ data: { current, latest, updated: true, report: REPORT_PATH },
1660
+ text: [
1661
+ `✓ Updated Pattern ${current} → ${latest}`,
1662
+ '',
1663
+ report,
1664
+ ` saved ${REPORT_PATH}`,
1665
+ ` next run app migration pattern_${latest.replaceAll('.', '_')} (it writes nothing when Pattern's own tables didn't change), do what the report says, then app check, then app publish --message 'Updated Pattern' (straight away if nothing else is in progress) so the live site gets it`,
1666
+ ...(instructions === undefined ? [] : [instructions])
1667
+ ].join('\n')
1644
1668
  };
1645
1669
  }
1670
+ function instructions() {
1671
+ const result = migrateInstructions(ROOT, join(SDK_PACKAGE, 'docs', 'history', 'agents.json'));
1672
+ if (result.status === 'none') {
1673
+ return {
1674
+ code: 1,
1675
+ data: result,
1676
+ text: '✗ There is no AGENTS.md here to move onto the guide.'
1677
+ };
1678
+ }
1679
+ if (result.removed === 0 && result.proposed === 0) {
1680
+ return {
1681
+ code: 0,
1682
+ data: result,
1683
+ text: result.status === 'proposed'
1684
+ ? `✓ AGENTS.md already imports Pattern's guide. Some sections still need a decision: follow ${PROPOSAL_PATH}, then delete it.`
1685
+ : "✓ AGENTS.md already imports Pattern's guide."
1686
+ };
1687
+ }
1688
+ return {
1689
+ code: 0,
1690
+ data: result,
1691
+ text: [
1692
+ `✓ AGENTS.md now imports Pattern's guide (node_modules/@trypattern/sdk/docs/PATTERN.md); ${result.removed} section${result.removed === 1 ? '' : 's'} of Pattern's old rules removed.`,
1693
+ result.proposed === 0
1694
+ ? ' next read node_modules/@trypattern/sdk/docs/PATTERN.md now: it replaces the rules you had.'
1695
+ : ` next ${result.proposed} section${result.proposed === 1 ? '' : 's'} may hold something written for this project: follow ${PROPOSAL_PATH}, then delete it. Read node_modules/@trypattern/sdk/docs/PATTERN.md first: it replaces the rules you had.`
1696
+ ].join('\n')
1697
+ };
1698
+ }
1699
+ function guidanceNotes() {
1700
+ return [
1701
+ pendingUpdate(ROOT, changelogIn(SDK_PACKAGE), sdkVersion()),
1702
+ instructionsNote(instructionsState(ROOT))
1703
+ ].filter((note) => note !== undefined);
1704
+ }
1646
1705
  async function setup() {
1647
1706
  const pins = readPins();
1648
1707
  const haveNode = process.versions.node;
@@ -1796,6 +1855,16 @@ async function main(argv) {
1796
1855
  };
1797
1856
  }
1798
1857
  }
1858
+ if (GUIDED_COMMANDS.has(name)) {
1859
+ const notes = guidanceNotes();
1860
+ if (notes.length > 0) {
1861
+ outcome = {
1862
+ ...outcome,
1863
+ data: { ...outcome.data, guidance: notes },
1864
+ text: [outcome.text, ...notes].join('\n')
1865
+ };
1866
+ }
1867
+ }
1799
1868
  }
1800
1869
  if (jsonMode) {
1801
1870
  const payload = {
@@ -0,0 +1,8 @@
1
+ export declare const GUIDE_PATH = "node_modules/@trypattern/sdk/docs/PATTERN.md";
2
+ export declare const REPORT_PATH: string;
3
+ export declare function changelogIn(directory: string): string;
4
+ export declare function updateReport(path: string, from: string, to: string): string;
5
+ export declare function patternFolder(root: string): string;
6
+ export declare function recordUpdate(root: string, changelogPath: string, from: string, to: string): string;
7
+ export declare function updatedNote(from: string, to: string): string;
8
+ export declare function pendingUpdate(root: string, changelogPath: string, current: string): string | undefined;
@@ -0,0 +1,121 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { isNewer } from "./updates.js";
6
+ export const GUIDE_PATH = 'node_modules/@trypattern/sdk/docs/PATTERN.md';
7
+ export const REPORT_PATH = join('.pattern', 'update-report.md');
8
+ const SEEN_PATH = join('.pattern', 'update.json');
9
+ const visualChange = z.object({
10
+ what: z.string(),
11
+ before: z.string(),
12
+ after: z.string(),
13
+ keep: z.string()
14
+ });
15
+ const release = z.object({
16
+ version: z.string(),
17
+ added: z.array(z.string()),
18
+ changed: z.array(z.string()),
19
+ visual: z.array(visualChange),
20
+ migrations: z.array(z.string())
21
+ });
22
+ const changelog = z.object({ versions: z.array(release) });
23
+ const seen = z.object({ reported: z.string() });
24
+ export function changelogIn(directory) {
25
+ return join(directory, 'docs', 'CHANGELOG.json');
26
+ }
27
+ function readJson(path) {
28
+ try {
29
+ return JSON.parse(readFileSync(path, 'utf8'));
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ function releasesBetween(path, from, to) {
36
+ const parsed = changelog.safeParse(readJson(path));
37
+ if (!parsed.success)
38
+ return [];
39
+ return parsed.data.versions
40
+ .filter((entry) => isNewer(entry.version, from) && !isNewer(entry.version, to))
41
+ .sort((a, b) => isNewer(a.version, b.version) ? 1 : isNewer(b.version, a.version) ? -1 : 0);
42
+ }
43
+ function listed(heading, releases, lines) {
44
+ const items = releases.flatMap((entry) => lines(entry).map((line) => `- ${entry.version} · ${line}`));
45
+ return items.length === 0 ? [] : [`## ${heading}`, '', ...items, ''];
46
+ }
47
+ export function updateReport(path, from, to) {
48
+ const releases = releasesBetween(path, from, to);
49
+ return [
50
+ `# Pattern ${from} → ${to}`,
51
+ '',
52
+ `Pattern was updated. Reread ${GUIDE_PATH} and the references you rely on: they changed with Pattern, and what you remember of them is from ${from}.`,
53
+ '',
54
+ ...listed('Do these now', releases, (entry) => entry.migrations),
55
+ ...(releases.some((entry) => entry.migrations.length > 0)
56
+ ? [
57
+ 'Then run app check, and publish when nothing else is in progress.',
58
+ ''
59
+ ]
60
+ : []),
61
+ ...listed('Looks different', releases, (entry) => entry.visual.map((change) => `${change.what}: ${change.before} Now: ${change.after}${change.keep === '' ? '' : ` To keep the old look: ${change.keep}`}`)),
62
+ ...listed('New', releases, (entry) => entry.added),
63
+ ...listed('Changed', releases, (entry) => entry.changed),
64
+ ...(releases.length === 0
65
+ ? ['Nothing in this update needs anything from you.', '']
66
+ : [])
67
+ ].join('\n');
68
+ }
69
+ export function patternFolder(root) {
70
+ const folder = join(root, '.pattern');
71
+ mkdirSync(folder, { recursive: true });
72
+ writeFileSync(join(folder, '.gitignore'), '*\n');
73
+ return folder;
74
+ }
75
+ function remember(root, version) {
76
+ patternFolder(root);
77
+ writeFileSync(join(root, SEEN_PATH), `${JSON.stringify({ reported: version }, null, 2)}\n`);
78
+ }
79
+ export function recordUpdate(root, changelogPath, from, to) {
80
+ const report = updateReport(changelogPath, from, to);
81
+ patternFolder(root);
82
+ writeFileSync(join(root, REPORT_PATH), report);
83
+ remember(root, to);
84
+ return report;
85
+ }
86
+ function committedVersion(root) {
87
+ const shown = spawnSync('git', ['show', 'HEAD:package.json'], {
88
+ cwd: root,
89
+ encoding: 'utf8'
90
+ });
91
+ if (shown.status !== 0)
92
+ return undefined;
93
+ try {
94
+ const manifest = JSON.parse(shown.stdout);
95
+ const dependencies = Reflect.get(Object(manifest), 'dependencies');
96
+ const version = Reflect.get(Object(dependencies), '@trypattern/sdk');
97
+ const exact = typeof version === 'string' ? version.replace(/^[\^~]/, '') : '';
98
+ return /^\d+\.\d+\.\d+/.test(exact) ? exact : undefined;
99
+ }
100
+ catch {
101
+ return undefined;
102
+ }
103
+ }
104
+ export function updatedNote(from, to) {
105
+ return ` updated Pattern ${from} → ${to}. Read ${REPORT_PATH} (what's new, what looks different and what to do now), do what it says, and reread ${GUIDE_PATH}: it changed with Pattern.`;
106
+ }
107
+ export function pendingUpdate(root, changelogPath, current) {
108
+ const remembered = seen.safeParse(readJson(join(root, SEEN_PATH)));
109
+ if (remembered.success && !isNewer(current, remembered.data.reported)) {
110
+ return undefined;
111
+ }
112
+ const from = remembered.success
113
+ ? remembered.data.reported
114
+ : committedVersion(root);
115
+ if (from === undefined || !isNewer(current, from)) {
116
+ remember(root, current);
117
+ return undefined;
118
+ }
119
+ recordUpdate(root, changelogPath, from, current);
120
+ return updatedNote(from, current);
121
+ }
@@ -0,0 +1,21 @@
1
+ export declare const GUIDE_IMPORT = "@node_modules/@trypattern/sdk/docs/PATTERN.md";
2
+ export declare const PROPOSAL_PATH: string;
3
+ type Section = {
4
+ raw: string;
5
+ text: string;
6
+ normalized: string;
7
+ };
8
+ export type InstructionsState = 'none' | 'migrated' | 'proposed' | 'old';
9
+ export declare function sectionsOf(markdown: string): Section[];
10
+ export declare function instructionsState(root: string): InstructionsState;
11
+ export declare function instructionsNote(state: InstructionsState): string | undefined;
12
+ export declare function migrateInstructions(root: string, historyPath: string): {
13
+ status: "none";
14
+ removed: number;
15
+ proposed: number;
16
+ } | {
17
+ status: "migrated" | "proposed";
18
+ removed: number;
19
+ proposed: number;
20
+ };
21
+ export {};
@@ -0,0 +1,206 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { GUIDE_PATH, patternFolder } from "./guidance.js";
6
+ export const GUIDE_IMPORT = `@${GUIDE_PATH}`;
7
+ export const PROPOSAL_PATH = join('.pattern', 'instructions-migration.md');
8
+ const GUIDE_PARAGRAPH = `Pattern's own rules (how to build, check and publish, and everything Pattern already does) come\nwith Pattern itself, so they stay current when it updates. Read them first, and reread them after\nPattern updates:\n\n${GUIDE_IMPORT}`;
9
+ const PROJECT_PATH = /`(?:src|server|starter|flows|public)\/|`(?:seed|schema)\.ts`|`wrangler\.jsonc`|`pattern\.json`/;
10
+ const owner = z.enum(['pattern', 'project', 'mixed']);
11
+ const history = z.object({
12
+ openers: z.array(z.object({ prefix: z.string(), owner, reference: z.string() })),
13
+ sections: z.record(z.string(), owner),
14
+ sentences: z.array(z.string())
15
+ });
16
+ function hashOf(text) {
17
+ return createHash('sha256').update(text).digest('hex').slice(0, 16);
18
+ }
19
+ function normalized(text) {
20
+ const collapsed = text.replace(/\s+/g, ' ').trim();
21
+ return collapsed.startsWith('- ') ? collapsed.slice(2) : collapsed;
22
+ }
23
+ function sentencesOf(text) {
24
+ return text.split(/(?<=[.!?;:])\s+/).filter((sentence) => sentence !== '');
25
+ }
26
+ function startsSection(line, previous) {
27
+ return (line.startsWith('- ') ||
28
+ /^#{1,6} /.test(line) ||
29
+ (line !== '' && !/^\s/.test(line) && previous === ''));
30
+ }
31
+ export function sectionsOf(markdown) {
32
+ const groups = [];
33
+ let previous = '';
34
+ for (const line of markdown.split('\n')) {
35
+ const current = groups.at(-1);
36
+ if (current === undefined || startsSection(line, previous)) {
37
+ groups.push([line]);
38
+ }
39
+ else {
40
+ current.push(line);
41
+ }
42
+ previous = line;
43
+ }
44
+ return groups
45
+ .map((lines) => `${lines.join('\n')}\n`)
46
+ .filter((raw) => raw.trim() !== '')
47
+ .map((raw) => {
48
+ const text = raw.replace(/\n+$/, '');
49
+ return { raw, text, normalized: normalized(text) };
50
+ });
51
+ }
52
+ function readHistory(path) {
53
+ try {
54
+ const parsed = history.safeParse(JSON.parse(readFileSync(path, 'utf8')));
55
+ return parsed.success ? parsed.data : undefined;
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ function openerOf(known, section) {
62
+ return known.openers.find((entry) => section.normalized.startsWith(entry.prefix));
63
+ }
64
+ function keptSentences(known, section, isEdited) {
65
+ const template = new Set(known.sentences);
66
+ return sentencesOf(section.normalized)
67
+ .filter((sentence) => (isEdited && !template.has(hashOf(sentence))) ||
68
+ PROJECT_PATH.test(sentence))
69
+ .join(' ');
70
+ }
71
+ function decide(known, section) {
72
+ const opener = openerOf(known, section);
73
+ const recorded = known.sections[hashOf(section.normalized)];
74
+ if (recorded === 'pattern')
75
+ return { kind: 'remove' };
76
+ if (recorded === 'project')
77
+ return { kind: 'keep' };
78
+ if (recorded === 'mixed') {
79
+ return {
80
+ kind: 'propose',
81
+ why: 'mixed',
82
+ reference: opener?.reference ?? 'PATTERN.md',
83
+ replacement: keptSentences(known, section, false)
84
+ };
85
+ }
86
+ if (opener === undefined || opener.owner === 'project') {
87
+ return { kind: 'keep' };
88
+ }
89
+ return {
90
+ kind: 'propose',
91
+ why: 'edited',
92
+ reference: opener.reference,
93
+ replacement: keptSentences(known, section, true)
94
+ };
95
+ }
96
+ function isIntro(section) {
97
+ return section.text.startsWith('# ') || !/^(?:- |#)/.test(section.text);
98
+ }
99
+ function joined(chunks) {
100
+ return chunks.reduce((text, chunk) => {
101
+ const needsGap = text !== '' && !text.endsWith('\n\n') && !chunk.startsWith('- ');
102
+ return `${text}${needsGap ? '\n' : ''}${chunk}`;
103
+ }, '');
104
+ }
105
+ function withGuide(kept) {
106
+ const first = kept.findIndex((section) => !isIntro(section));
107
+ const at = first === -1 ? kept.length : first;
108
+ return joined([
109
+ ...kept.slice(0, at).map((section) => section.raw),
110
+ `${GUIDE_PARAGRAPH}\n\n`,
111
+ ...kept.slice(at).map((section) => section.raw)
112
+ ]).replace(/\n+$/, '\n');
113
+ }
114
+ function proposalFor(proposals) {
115
+ const items = proposals.flatMap(({ section, decision }, index) => {
116
+ if (decision.kind !== 'propose')
117
+ return [];
118
+ const why = decision.why === 'mixed'
119
+ ? `This is the old template's text. Its rules now live in Pattern's guide (node_modules/@trypattern/sdk/docs/${decision.reference}); only the lines about this project's own files belong in AGENTS.md.`
120
+ : `This looks like one of Pattern's old sections, changed for this project. Pattern's version now lives in node_modules/@trypattern/sdk/docs/${decision.reference}; keep only what's true and specific to this project.`;
121
+ return [
122
+ `## ${index + 1}. ${section.normalized.slice(0, 60)}…`,
123
+ '',
124
+ why,
125
+ '',
126
+ 'Now in AGENTS.md:',
127
+ '',
128
+ '```md',
129
+ section.text,
130
+ '```',
131
+ '',
132
+ decision.replacement === ''
133
+ ? 'Proposed: delete it.'
134
+ : 'Proposed replacement:',
135
+ '',
136
+ ...(decision.replacement === ''
137
+ ? []
138
+ : ['```md', `- ${decision.replacement}`, '```', ''])
139
+ ];
140
+ });
141
+ return [
142
+ '# Finish moving AGENTS.md onto Pattern’s guide',
143
+ '',
144
+ `app migrate-instructions moved AGENTS.md onto Pattern's guide (${GUIDE_PATH}) and removed the rules Pattern now ships. It kept the sections below, because they may hold something written for this project. For each one, read it next to the guide, then apply the proposal, change it, or keep the section as it is. Check every fact against the project's files before keeping it.`,
145
+ '',
146
+ 'When every section is decided, delete this file. app check and app start say "instructions migration incomplete" until it is gone.',
147
+ '',
148
+ ...items
149
+ ].join('\n');
150
+ }
151
+ export function instructionsState(root) {
152
+ const path = join(root, 'AGENTS.md');
153
+ if (!existsSync(path))
154
+ return 'none';
155
+ if (!readFileSync(path, 'utf8').includes(GUIDE_IMPORT))
156
+ return 'old';
157
+ return existsSync(join(root, PROPOSAL_PATH)) ? 'proposed' : 'migrated';
158
+ }
159
+ export function instructionsNote(state) {
160
+ if (state === 'old') {
161
+ return " instructions migration incomplete AGENTS.md still carries Pattern's old rules, so it may say Pattern can't do things it now does. Run app migrate-instructions and follow what it prints.";
162
+ }
163
+ if (state === 'proposed') {
164
+ return ` instructions migration incomplete Some sections of AGENTS.md need a decision: follow ${PROPOSAL_PATH}, then delete it.`;
165
+ }
166
+ return undefined;
167
+ }
168
+ export function migrateInstructions(root, historyPath) {
169
+ const path = join(root, 'AGENTS.md');
170
+ const state = instructionsState(root);
171
+ if (state === 'none') {
172
+ return { status: 'none', removed: 0, proposed: 0 };
173
+ }
174
+ if (state !== 'old') {
175
+ return { status: state, removed: 0, proposed: 0 };
176
+ }
177
+ const known = readHistory(historyPath);
178
+ if (known === undefined) {
179
+ throw new Error(`Pattern's instructions history is missing (${historyPath})`);
180
+ }
181
+ const decided = sectionsOf(readFileSync(path, 'utf8')).map((section) => ({
182
+ section,
183
+ decision: decide(known, section)
184
+ }));
185
+ const kept = decided
186
+ .filter(({ decision }) => decision.kind !== 'remove')
187
+ .map(({ section }) => section);
188
+ const proposals = decided.filter(({ decision }) => decision.kind === 'propose');
189
+ writeFileSync(path, withGuide(kept));
190
+ if (!existsSync(join(root, 'CLAUDE.md'))) {
191
+ writeFileSync(join(root, 'CLAUDE.md'), '@AGENTS.md\n');
192
+ }
193
+ const proposalPath = join(root, PROPOSAL_PATH);
194
+ if (proposals.length === 0) {
195
+ rmSync(proposalPath, { force: true });
196
+ }
197
+ else {
198
+ patternFolder(root);
199
+ writeFileSync(proposalPath, proposalFor(proposals));
200
+ }
201
+ return {
202
+ status: proposals.length === 0 ? 'migrated' : 'proposed',
203
+ removed: decided.length - kept.length,
204
+ proposed: proposals.length
205
+ };
206
+ }
@@ -1,3 +1,4 @@
1
+ import type { WeekStart } from './calendarDates.ts';
1
2
  import type { Placement } from './Placement.ts';
2
3
  export type CalendarDay = {
3
4
  date: string;
@@ -14,7 +15,8 @@ type CalendarProps = {
14
15
  lastMonth?: string;
15
16
  today?: string;
16
17
  locale?: string;
18
+ weekStart?: WeekStart;
17
19
  style?: Placement;
18
20
  };
19
- export declare function Calendar({ label, month, onMonthChange, days, value, onPick, firstMonth, lastMonth, today, locale, style }: CalendarProps): import("react").JSX.Element;
21
+ export declare function Calendar({ label, month, onMonthChange, days, value, onPick, firstMonth, lastMonth, today, locale, weekStart, style }: CalendarProps): import("react").JSX.Element;
20
22
  export {};
@@ -7,7 +7,7 @@ import { addDays, addMonths, dayLabel, dayNumber, localToday, monthOf, monthTitl
7
7
  import { focusRing } from "./controlLook.js";
8
8
  import { Icon } from "./Icon.js";
9
9
  import { interact } from "./interaction.js";
10
- export function Calendar({ label, month, onMonthChange, days, value, onPick, firstMonth, lastMonth, today = localToday(), locale, style }) {
10
+ export function Calendar({ label, month, onMonthChange, days, value, onPick, firstMonth, lastMonth, today = localToday(), locale, weekStart = 'monday', style }) {
11
11
  const states = new Map(days.map((day) => [day.date, day.state]));
12
12
  const [moved, setMoved] = useState(null);
13
13
  const grid = useRef(null);
@@ -46,7 +46,7 @@ export function Calendar({ label, month, onMonthChange, days, value, onPick, fir
46
46
  if (step === undefined)
47
47
  return;
48
48
  event.preventDefault();
49
- const target = step(date);
49
+ const target = step(date, weekStart);
50
50
  const targetMonth = monthOf(target);
51
51
  if (firstMonth !== undefined && targetMonth < firstMonth)
52
52
  return;
@@ -57,7 +57,7 @@ export function Calendar({ label, month, onMonthChange, days, value, onPick, fir
57
57
  if (targetMonth !== month)
58
58
  goTo(targetMonth);
59
59
  };
60
- return (_jsxs("div", { ...stylex.props(styles.calendar, style), children: [_jsxs("div", { ...stylex.props(styles.header), children: [_jsx("span", { ...stylex.props(styles.title), "aria-live": "polite", children: monthTitle(month, locale) }), _jsxs("span", { ...stylex.props(styles.nav), children: [_jsx(BaseButton, { "aria-label": monthTitle(previous, locale), disabled: !canGoBack, onClick: () => goTo(previous), className: stylex.props(styles.navButton, canGoBack ? styles.navEnabled : styles.navDisabled, focusRing.visible).className, children: _jsx(Icon, { name: "previous" }) }), _jsx(BaseButton, { "aria-label": monthTitle(next, locale), disabled: !canGoOn, onClick: () => goTo(next), className: stylex.props(styles.navButton, canGoOn ? styles.navEnabled : styles.navDisabled, focusRing.visible).className, children: _jsx(Icon, { name: "next" }) })] })] }), _jsxs("div", { ref: grid, role: "grid", "aria-label": `${label}, ${monthTitle(month, locale)}`, ...stylex.props(styles.grid), children: [_jsx("div", { role: "row", ...stylex.props(styles.row), children: weekdayNames(locale).map((weekday) => (_jsx("span", { role: "columnheader", "aria-label": weekday.long, ...stylex.props(styles.weekday), children: weekday.short }, weekday.long))) }), weeksOf(month).map((week, index) => (_jsx("div", { role: "row", ...stylex.props(styles.row), children: week.map((date, column) => {
60
+ return (_jsxs("div", { ...stylex.props(styles.calendar, style), children: [_jsxs("div", { ...stylex.props(styles.header), children: [_jsx("span", { ...stylex.props(styles.title), "aria-live": "polite", children: monthTitle(month, locale) }), _jsxs("span", { ...stylex.props(styles.nav), children: [_jsx(BaseButton, { "aria-label": monthTitle(previous, locale), disabled: !canGoBack, onClick: () => goTo(previous), className: stylex.props(styles.navButton, canGoBack ? styles.navEnabled : styles.navDisabled, focusRing.visible).className, children: _jsx(Icon, { name: "previous" }) }), _jsx(BaseButton, { "aria-label": monthTitle(next, locale), disabled: !canGoOn, onClick: () => goTo(next), className: stylex.props(styles.navButton, canGoOn ? styles.navEnabled : styles.navDisabled, focusRing.visible).className, children: _jsx(Icon, { name: "next" }) })] })] }), _jsxs("div", { ref: grid, role: "grid", "aria-label": `${label}, ${monthTitle(month, locale)}`, ...stylex.props(styles.grid), children: [_jsx("div", { role: "row", ...stylex.props(styles.row), children: weekdayNames(locale, weekStart).map((weekday) => (_jsx("span", { role: "columnheader", "aria-label": weekday.long, ...stylex.props(styles.weekday), children: weekday.short }, weekday.long))) }), weeksOf(month, weekStart).map((week, index) => (_jsx("div", { role: "row", ...stylex.props(styles.row), children: week.map((date, column) => {
61
61
  if (date === null) {
62
62
  return (_jsx("span", { role: "gridcell", ...stylex.props(styles.cell) }, `blank-${column}`));
63
63
  }
@@ -72,8 +72,8 @@ const MOVES = {
72
72
  ArrowRight: (date) => addDays(date, 1),
73
73
  ArrowUp: (date) => addDays(date, -7),
74
74
  ArrowDown: (date) => addDays(date, 7),
75
- Home: (date) => addDays(date, -weekdayIndex(date)),
76
- End: (date) => addDays(date, 6 - weekdayIndex(date)),
75
+ Home: (date, weekStart) => addDays(date, -weekdayIndex(date, weekStart)),
76
+ End: (date, weekStart) => addDays(date, 6 - weekdayIndex(date, weekStart)),
77
77
  PageUp: (date) => addMonths(date, -1),
78
78
  PageDown: (date) => addMonths(date, 1)
79
79
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
3
3
  import * as stylex from '@stylexjs/stylex';
4
- import { borders, colors, iconSizes, motion, radii, space } from "../theme/tokens.stylex.js";
4
+ import { borders, colors, corners, iconSizes, motion, radii, space } from "../theme/tokens.stylex.js";
5
5
  import { focusRing } from "./controlLook.js";
6
6
  import { interact } from "./interaction.js";
7
7
  export function Checkbox({ label, checked, disabled = false, shape = 'round', onChange, style }) {
@@ -33,7 +33,7 @@ const styles = stylex.create({
33
33
  }
34
34
  },
35
35
  square: {
36
- borderRadius: radii.sm
36
+ borderRadius: corners.checkbox
37
37
  },
38
38
  off: {
39
39
  backgroundColor: 'transparent',
@@ -55,7 +55,7 @@ export function Dialog({ label, isOpen, onOpenChange, placement = 'center', size
55
55
  onOpenChange(next);
56
56
  }, children: _jsxs(BaseDialog.Portal, { children: [placement === 'center' || isSheet ? (_jsx(BaseDialog.Backdrop, { className: (state) => stylex.props(styles.backdrop, (!state.open || state.transitionStatus === 'starting') &&
57
57
  styles.backdropHidden).className })) : null, _jsx(BaseDialog.Popup, { "aria-label": label, className: (state) => stylex.props(styles.popup, placements[placement], isSheet ? sheetWidths[size] : widths[size], (!state.open || state.transitionStatus === 'starting') &&
58
- hiddenPlacements[placement], !state.open && styles.exiting, isSheet && drag > 0 && styles.dragged(drag)).className, children: _jsxs("div", { ...stylex.props(styles.frame, isSheet && styles.sheetFrame), children: [isSheet ? (_jsx("div", { "aria-hidden": "true", ref: handle, ...stylex.props(styles.handle), children: _jsx("span", { ...stylex.props(styles.grip) }) })) : null, children] }) })] }) }));
58
+ hiddenPlacements[placement], !state.open && styles.exiting, isSheet && drag > 0 && styles.dragged(drag)).className, children: _jsxs("div", { ...stylex.props(styles.frame, isSheet && styles.sheetFrame), children: [isSheet ? (_jsx("div", { "aria-hidden": "true", ref: handle, ...stylex.props(styles.handle), children: _jsx("span", { ...stylex.props(styles.grip) }) })) : null, _jsx("div", { ...stylex.props(styles.content), children: children })] }) })] }) }));
59
59
  }
60
60
  const styles = stylex.create({
61
61
  backdrop: {
@@ -120,6 +120,15 @@ const styles = stylex.create({
120
120
  backgroundColor: colors.borderStrong,
121
121
  borderRadius: radii.full
122
122
  },
123
+ content: {
124
+ display: 'flex',
125
+ flexGrow: 1,
126
+ flexShrink: 1,
127
+ flexDirection: 'column',
128
+ minHeight: 0,
129
+ overflowY: 'auto',
130
+ overscrollBehaviorY: 'contain'
131
+ },
123
132
  frame: {
124
133
  display: 'flex',
125
134
  flexDirection: 'column',
@@ -13,11 +13,13 @@ const styles = stylex.create({
13
13
  height: '100%',
14
14
  minHeight: 0,
15
15
  overflowY: 'auto',
16
- overscrollBehavior: 'contain'
16
+ overscrollBehaviorX: 'contain',
17
+ overscrollBehaviorY: 'contain'
17
18
  },
18
19
  horizontal: {
19
20
  height: 'auto',
20
21
  overflowX: 'auto',
21
- overflowY: 'hidden'
22
+ overflowY: 'hidden',
23
+ overscrollBehaviorY: 'auto'
22
24
  }
23
25
  });
@@ -6,6 +6,7 @@ export type SelectOption<Value extends string> = {
6
6
  };
7
7
  type SelectProps<Value extends string> = {
8
8
  label: string;
9
+ isLabelHidden?: boolean;
9
10
  value: Value;
10
11
  options: readonly SelectOption<Value>[];
11
12
  size?: Size;
@@ -13,5 +14,5 @@ type SelectProps<Value extends string> = {
13
14
  onChange: (value: Value) => void;
14
15
  style?: Placement;
15
16
  };
16
- export declare function Select<Value extends string>({ label, value, options, size, disabled, onChange, style }: SelectProps<Value>): import("react").JSX.Element;
17
+ export declare function Select<Value extends string>({ label, isLabelHidden, value, options, size, disabled, onChange, style }: SelectProps<Value>): import("react").JSX.Element;
17
18
  export {};