diff --git a/.github/scripts/change-scope.cjs b/.github/scripts/change-scope.cjs index 1ac435847ba90..562320a984382 100644 --- a/.github/scripts/change-scope.cjs +++ b/.github/scripts/change-scope.cjs @@ -4,7 +4,10 @@ /* global console, module, process, require */ // eslint-disable-next-line @typescript-eslint/no-require-imports -const {isComponentSpecRecordPath} = require('./knowledge-paths.cjs'); +const { + isComponentSpecRecordPath, + isPackageArchitectureRecordPath, +} = require('./knowledge-paths.cjs'); /** * Classifies changed paths without reading PR-controlled content. @@ -99,6 +102,7 @@ function isPackageReleasePath(filePath) { function isKnowledgeRecordPath(filePath) { return ( isSpecRecordPath(filePath) || + isPackageArchitectureRecordPath(filePath) || KNOWLEDGE_RECORD_PATTERNS.some(pattern => pattern.test(filePath)) ); } diff --git a/.github/scripts/change-scope.test.mjs b/.github/scripts/change-scope.test.mjs index 0161155f805e8..a6981c2b5437d 100644 --- a/.github/scripts/change-scope.test.mjs +++ b/.github/scripts/change-scope.test.mjs @@ -39,6 +39,22 @@ describe('spec-only change scope', () => { expect(result.specOnly).toBe(false); }); + it('treats a platform component record as a spec record', () => { + const result = classifyChanges([ + {filename: 'packages/native/apple/Sources/Astryx/Button/Button.spec.md'}, + ]); + expect(result.touchesKnowledgeRecords).toBe(true); + expect(result.specOnly).toBe(true); + }); + + it('owner-gates package records like architecture records', () => { + const result = classifyChanges([ + {filename: 'packages/native/specs/swift/accessibility.spec.md'}, + ]); + expect(result.touchesKnowledgeRecords).toBe(true); + expect(result.specOnly).toBe(false); + }); + it('owner-gates normative design assets without granting the spec-only fast path', () => { const result = classifyChanges([ {filename: 'docs/design/assets/selector/alignment.png'}, diff --git a/.github/scripts/knowledge-paths.cjs b/.github/scripts/knowledge-paths.cjs index 1ec206fbe5903..4f56405122cc7 100644 --- a/.github/scripts/knowledge-paths.cjs +++ b/.github/scripts/knowledge-paths.cjs @@ -4,9 +4,11 @@ /* global module, require */ /** - * One shared path contract for component-local knowledge records. It consumes - * the component-package registry so validation, change classification, and the - * spec-owner gate cannot disagree about supported package layouts. + * One shared path contract for component-local knowledge records, platform + * component records, package records, and the spec-shaped files that must be + * records (INV27). It consumes the component-package registry so validation, + * change classification, and the spec-owner gate cannot disagree about + * supported package layouts. */ // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -93,14 +95,112 @@ function classifyComponentKnowledgePath(filePath) { }; } +/** + * Platform implementation packages and the language their component records + * name (`component:/`). Each component root is a + * directory of the package's component tree; its record is the direct child + * `.spec.md`. Swift has one package, `packages/native/apple`, + * whose `Astryx` target holds one folder per component. + */ +const PLATFORM_COMPONENT_ROOTS = Object.freeze([ + Object.freeze({ + language: 'swift', + pattern: /^(packages\/native\/apple\/Sources\/Astryx)\/([^/]+)\/([^/]+)$/, + }), +]); + +/** + * Classify a platform implementation's component record by path alone: + * `//.spec.md`. Its id, its + * `platform_of` link, and the core record it names are validated after + * parsing. + */ +function classifyPlatformComponentKnowledgePath(filePath) { + const normalized = normalizePath(filePath); + if (!normalized.endsWith('.spec.md')) return null; + for (const {language, pattern} of PLATFORM_COMPONENT_ROOTS) { + const match = pattern.exec(normalized); + if (!match) continue; + const [, componentTree, componentRoot, fileName] = match; + if (isIgnoredComponentKnowledgeSegment(componentRoot)) return null; + if (fileName.endsWith('.generated.spec.md')) return null; + const publicName = fileName.slice(0, -'.spec.md'.length); + if (!/^[A-Z][A-Za-z0-9]*$/.test(publicName)) return null; + return { + componentRoot, + componentTree, + fileName, + kind: 'component', + language, + layout: 'platform', + packageName: normalized.split('/')[1], + publicName, + relativePath: fileName, + }; + } + return null; +} + +/** + * Classify a package's cross-component record: a `.spec.md` anywhere + * under `packages//specs/`. Its kind is `architecture` and its id is + * `architecture:--`, joined with hyphens. + */ +function classifyPackageArchitecturePath(filePath) { + const match = + /^packages\/([a-z0-9][a-z0-9-]*)\/specs\/((?:[a-z0-9][a-z0-9-]*\/)*)([a-z0-9][a-z0-9-]*)\.spec\.md$/.exec( + normalizePath(filePath), + ); + if (!match) return null; + const [, packageName, directories, slug] = match; + const parts = [packageName, ...directories.split('/').filter(Boolean), slug]; + return { + id: `architecture:${parts.join('-')}`, + kind: 'architecture', + packageName, + }; +} + +/** + * A spec-shaped file that must be a valid knowledge record (INV27): a tracked + * Markdown file named `*.spec.md`, or any Markdown file under a directory + * named `specs`. README.md indexes, plan.md implementation plans, and paths + * discovery ignores (fixtures, tests, generated output, hidden directories) + * are not candidates. + */ +function isKnowledgeRecordCandidatePath(filePath) { + const normalized = normalizePath(filePath); + if (!normalized.endsWith('.md')) return false; + const segments = normalized.split('/'); + const fileName = segments.at(-1); + if (fileName === 'README.md' || fileName === 'plan.md') return false; + if (fileName.startsWith('.')) return false; + if (fileName.endsWith('.generated.spec.md')) return false; + const directories = segments.slice(0, -1); + if (directories.some(isIgnoredComponentKnowledgeSegment)) return false; + return fileName.endsWith('.spec.md') || directories.includes('specs'); +} + function isComponentSpecRecordPath(filePath) { - return classifyComponentKnowledgePath(filePath) !== null; + return ( + classifyComponentKnowledgePath(filePath) !== null || + classifyPlatformComponentKnowledgePath(filePath) !== null + ); +} + +function isPackageArchitectureRecordPath(filePath) { + return classifyPackageArchitecturePath(filePath) !== null; } module.exports = { COMPONENT_PACKAGE_NAMES, IGNORED_COMPONENT_KNOWLEDGE_SEGMENTS, + PLATFORM_COMPONENT_ROOTS, classifyComponentKnowledgePath, + classifyPackageArchitecturePath, + classifyPlatformComponentKnowledgePath, isComponentSpecRecordPath, isIgnoredComponentKnowledgeSegment, + isKnowledgeRecordCandidatePath, + isPackageArchitectureRecordPath, }; diff --git a/.github/scripts/knowledge-paths.test.mjs b/.github/scripts/knowledge-paths.test.mjs index e1fd98fc874f4..f3d9e623a010d 100644 --- a/.github/scripts/knowledge-paths.test.mjs +++ b/.github/scripts/knowledge-paths.test.mjs @@ -6,7 +6,10 @@ import {describe, expect, it} from 'vitest'; const require = createRequire(import.meta.url); const { classifyComponentKnowledgePath, + classifyPackageArchitecturePath, + classifyPlatformComponentKnowledgePath, isComponentSpecRecordPath, + isKnowledgeRecordCandidatePath, } = require('./knowledge-paths.cjs'); describe('component knowledge paths', () => { @@ -74,3 +77,85 @@ describe('component knowledge paths', () => { expect(classifyComponentKnowledgePath(filePath)).toBeNull(); }); }); + +describe('platform component knowledge paths', () => { + it('classifies a Swift component record beside its code', () => { + const filePath = + 'packages/native/apple/Sources/Astryx/Button/Button.spec.md'; + expect(classifyPlatformComponentKnowledgePath(filePath)).toMatchObject({ + componentRoot: 'Button', + componentTree: 'packages/native/apple/Sources/Astryx', + kind: 'component', + language: 'swift', + publicName: 'Button', + }); + expect(isComponentSpecRecordPath(filePath)).toBe(true); + }); + + it.each([ + 'packages/native/apple/Sources/Astryx/Button/plugins/Thing.spec.md', + 'packages/native/apple/Sources/Astryx/Button.spec.md', + 'packages/native/apple/Sources/AstryxNative/Button/Button.spec.md', + 'packages/native/apple/Sources/Astryx/__fixtures__/Button.spec.md', + 'packages/native/apple/Sources/Astryx/Button/button.spec.md', + 'packages/native/swift-ios-1/core/Button/Button.spec.md', + ])('rejects %s', filePath => { + expect(classifyPlatformComponentKnowledgePath(filePath)).toBeNull(); + }); +}); + +describe('package record paths', () => { + it.each([ + [ + 'packages/native/specs/swift/accessibility.spec.md', + 'architecture:native-swift-accessibility', + ], + [ + 'packages/native/specs/swift/library/theme-generator.spec.md', + 'architecture:native-swift-library-theme-generator', + ], + [ + 'packages/native/specs/boundaries.spec.md', + 'architecture:native-boundaries', + ], + ])('derives the id of %s', (filePath, id) => { + expect(classifyPackageArchitecturePath(filePath)).toEqual({ + id, + kind: 'architecture', + packageName: 'native', + }); + }); + + it.each([ + 'packages/native/specs/swift/accessibility.md', + 'packages/native/specs/swift/Accessibility.spec.md', + 'packages/native/docs/swift/accessibility.spec.md', + ])('rejects %s', filePath => { + expect(classifyPackageArchitecturePath(filePath)).toBeNull(); + }); +}); + +describe('knowledge record candidates (INV27)', () => { + it.each([ + 'packages/native/specs/TEMPLATE.md', + 'packages/native/specs/swift/library/accessibility.md', + 'packages/widgets/docs/Widget.spec.md', + 'docs/specs/AST-082-native-first-surfaces/spec.md', + 'packages/core/src/Button/Button.spec.md', + ])('treats %s as a candidate', filePath => { + expect(isKnowledgeRecordCandidatePath(filePath)).toBe(true); + }); + + it.each([ + 'packages/native/specs/README.md', + 'docs/specs/AST-082-native-first-surfaces/plan.md', + 'packages/native/specs/__fixtures__/fixture.md', + 'packages/core/src/Button/node_modules/dep/Dep.spec.md', + 'packages/core/src/Button/.Hidden.spec.md', + 'packages/core/src/Button/Thing.generated.spec.md', + 'packages/native/specs/swift/notes.txt', + 'docs/contributing/spec-writing.md', + ])('does not treat %s as a candidate', filePath => { + expect(isKnowledgeRecordCandidatePath(filePath)).toBe(false); + }); +}); diff --git a/docs/architecture/knowledge-contracts.md b/docs/architecture/knowledge-contracts.md index a2a2d24b328d6..48d97f68fba80 100644 --- a/docs/architecture/knowledge-contracts.md +++ b/docs/architecture/knowledge-contracts.md @@ -1012,13 +1012,6 @@ Evidence placement is enforced by contributors and reviewers. The local `docs/pr-assets/` ignore prevents accidental staging; it is not a blocking CI check and does not prevent force-added evidence elsewhere. -Current enforcement gap: discovery does not yet read record candidates outside -registered component packages, themes, and `docs/`; component records have no -`platform_of` field until the next schema version; and the component id -pattern does not yet accept a language-qualified id such as -`component:swift/Button`. Until all three land, records under -`packages/native/` are checked only by review. - Current enforcement gap: no checked-in gate yet proves the open-pull-request search. Until one exists, the pull-request summary records the search terms, canonical owner/path, and overlapping open work inspected. diff --git a/docs/schemas/knowledge/v5.json b/docs/schemas/knowledge/v5.json new file mode 100644 index 0000000000000..5b70b62ae55ce --- /dev/null +++ b/docs/schemas/knowledge/v5.json @@ -0,0 +1,57 @@ +{ + "schemaVersion": 5, + "extends": 4, + "kinds": { + "component": { + "template": "docs/templates/knowledge/component-spec.md", + "requiredFrontmatter": [ + "schema_version", + "template_version", + "kind", + "id", + "platform_of", + "authority", + "archive_reason", + "superseded_by", + "approved_by", + "approved_at", + "owners", + "review_triggers", + "verified_by", + "modules", + "families", + "design_specs", + "architecture", + "contributing", + "system_specs" + ], + "requiredSections": [ + "Intent", + "Compatibility and migration", + "Ownership boundary", + "Public concepts", + "Behavioral and layout contract", + "Accessibility contract", + "Design relationships", + "Family and system relationships", + "Verification map", + "Decision log", + "Open questions", + "Content boundary" + ], + "currentNonEmptyFields": ["owners", "review_triggers", "verified_by"], + "listFields": [ + "owners", + "review_triggers", + "verified_by", + "modules", + "families", + "design_specs", + "architecture", + "contributing", + "system_specs" + ], + "idPattern": "^component:(?:[a-z][a-z0-9]*/)?[A-Z][A-Za-z0-9]*$" + } + } +} diff --git a/docs/specs/AST-093-native-component-guidelines/spec.md b/docs/specs/AST-093-native-component-guidelines/spec.md index e544890340500..41b81e07723cf 100644 --- a/docs/specs/AST-093-native-component-guidelines/spec.md +++ b/docs/specs/AST-093-native-component-guidelines/spec.md @@ -143,11 +143,9 @@ validating, and reviewing individual native components while - `@astryxdesign/native` gains `packages/native/specs/`, holding one directory per language, starting with `swift/`. The directory is not published. -- The Swift specs under `packages/native/specs/swift/` and the package template - `packages/native/specs/TEMPLATE.md` predate FR1's record form. They convert - to knowledge records in one change, which removes the template. Until then, - the package READMEs still name the template, and a new component spec follows - FR1 instead. +- The Swift rules under `packages/native/specs/swift/` are architecture + records (`architecture:native-swift-`), and the package keeps no + template of its own. - No architecture, family, contributing, or consumer-doc record changes. - No native component spec exists yet. - Component folders live inside a language's implementation package. For diff --git a/docs/templates/knowledge/component-spec.md b/docs/templates/knowledge/component-spec.md index f44ac8d9ec82b..248e31e21185a 100644 --- a/docs/templates/knowledge/component-spec.md +++ b/docs/templates/knowledge/component-spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 -template_version: 7 +schema_version: 5 +template_version: 8 kind: component id: component: +platform_of: null authority: draft archive_reason: null superseded_by: null @@ -23,6 +24,8 @@ system_specs: [spec:AST-000/DEC-0] + + ## Contract at a glance | Area | Contract | diff --git a/docs/templates/knowledge/versions.json b/docs/templates/knowledge/versions.json index 474f7de24bfcf..9723d565f2004 100644 --- a/docs/templates/knowledge/versions.json +++ b/docs/templates/knowledge/versions.json @@ -1,6 +1,6 @@ { "architecture": 3, - "component": 7, + "component": 8, "design": 2, "family": 2, "implementation-plan": 1, diff --git a/packages/charts/src/Chart.spec.md b/packages/charts/src/Chart.spec.md index fae12eb57ed68..44a7618c8c672 100644 --- a/packages/charts/src/Chart.spec.md +++ b/packages/charts/src/Chart.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:Chart +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/charts/src/ChartAxis.spec.md b/packages/charts/src/ChartAxis.spec.md index 5edbd258c023f..03fe293c31756 100644 --- a/packages/charts/src/ChartAxis.spec.md +++ b/packages/charts/src/ChartAxis.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChartAxis +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/charts/src/ChartGrid.spec.md b/packages/charts/src/ChartGrid.spec.md index 19cb25b005775..213e6c120c912 100644 --- a/packages/charts/src/ChartGrid.spec.md +++ b/packages/charts/src/ChartGrid.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChartGrid +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/charts/src/ChartLegend.spec.md b/packages/charts/src/ChartLegend.spec.md index 3be2294ebed5c..8870f0573f537 100644 --- a/packages/charts/src/ChartLegend.spec.md +++ b/packages/charts/src/ChartLegend.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChartLegend +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/charts/src/ChartSwatch.spec.md b/packages/charts/src/ChartSwatch.spec.md index 3ed40a75d66ac..bd4f5fc3f2642 100644 --- a/packages/charts/src/ChartSwatch.spec.md +++ b/packages/charts/src/ChartSwatch.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChartSwatch +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/charts/src/ChartTooltip.spec.md b/packages/charts/src/ChartTooltip.spec.md index fdec5aa7f5758..dc0af5f40d99a 100644 --- a/packages/charts/src/ChartTooltip.spec.md +++ b/packages/charts/src/ChartTooltip.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChartTooltip +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/AlertDialog/AlertDialog.spec.md b/packages/core/src/AlertDialog/AlertDialog.spec.md index 668faec508ebb..58cbe0fb4c53f 100644 --- a/packages/core/src/AlertDialog/AlertDialog.spec.md +++ b/packages/core/src/AlertDialog/AlertDialog.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:AlertDialog +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/AppShell/AppShell.spec.md b/packages/core/src/AppShell/AppShell.spec.md index b48dad65841e7..6dfe5d1ba1904 100644 --- a/packages/core/src/AppShell/AppShell.spec.md +++ b/packages/core/src/AppShell/AppShell.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:AppShell +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/AspectRatio/AspectRatio.spec.md b/packages/core/src/AspectRatio/AspectRatio.spec.md index 8403948807f99..c01c327cb5b48 100644 --- a/packages/core/src/AspectRatio/AspectRatio.spec.md +++ b/packages/core/src/AspectRatio/AspectRatio.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:AspectRatio +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Avatar/Avatar.spec.md b/packages/core/src/Avatar/Avatar.spec.md index 5190d3e8d28c5..857908aa07df3 100644 --- a/packages/core/src/Avatar/Avatar.spec.md +++ b/packages/core/src/Avatar/Avatar.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Avatar +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Avatar/AvatarStatusDot.spec.md b/packages/core/src/Avatar/AvatarStatusDot.spec.md index 4e202d1434ff1..2db7d9f04eb4e 100644 --- a/packages/core/src/Avatar/AvatarStatusDot.spec.md +++ b/packages/core/src/Avatar/AvatarStatusDot.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:AvatarStatusDot +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/AvatarGroup/AvatarGroup.spec.md b/packages/core/src/AvatarGroup/AvatarGroup.spec.md index 754e454f4e745..48cf5a802015b 100644 --- a/packages/core/src/AvatarGroup/AvatarGroup.spec.md +++ b/packages/core/src/AvatarGroup/AvatarGroup.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:AvatarGroup +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/AvatarGroup/AvatarGroupOverflow.spec.md b/packages/core/src/AvatarGroup/AvatarGroupOverflow.spec.md index 7fb62fc126e02..c32b75aff6ed2 100644 --- a/packages/core/src/AvatarGroup/AvatarGroupOverflow.spec.md +++ b/packages/core/src/AvatarGroup/AvatarGroupOverflow.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:AvatarGroupOverflow +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Badge/Badge.spec.md b/packages/core/src/Badge/Badge.spec.md index 5c961292e92cf..0c956b194373b 100644 --- a/packages/core/src/Badge/Badge.spec.md +++ b/packages/core/src/Badge/Badge.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Badge +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Banner/Banner.spec.md b/packages/core/src/Banner/Banner.spec.md index 57118b806d736..9627f3ddc405e 100644 --- a/packages/core/src/Banner/Banner.spec.md +++ b/packages/core/src/Banner/Banner.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:Banner +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Blockquote/Blockquote.spec.md b/packages/core/src/Blockquote/Blockquote.spec.md index b91431bc4b615..4ef432b890c88 100644 --- a/packages/core/src/Blockquote/Blockquote.spec.md +++ b/packages/core/src/Blockquote/Blockquote.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Blockquote +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/BottomSheet/BottomSheet.spec.md b/packages/core/src/BottomSheet/BottomSheet.spec.md index 382c1a90c74e6..a761241e2d3bd 100644 --- a/packages/core/src/BottomSheet/BottomSheet.spec.md +++ b/packages/core/src/BottomSheet/BottomSheet.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:BottomSheet +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/BottomSheet/BottomSheetSwitcher.spec.md b/packages/core/src/BottomSheet/BottomSheetSwitcher.spec.md index 10bf169f4aa8d..329fe6d3e0b2c 100644 --- a/packages/core/src/BottomSheet/BottomSheetSwitcher.spec.md +++ b/packages/core/src/BottomSheet/BottomSheetSwitcher.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:BottomSheetSwitcher +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Breadcrumbs/BreadcrumbItem.spec.md b/packages/core/src/Breadcrumbs/BreadcrumbItem.spec.md index 4cac1c5cd229a..ecc3374fa7b61 100644 --- a/packages/core/src/Breadcrumbs/BreadcrumbItem.spec.md +++ b/packages/core/src/Breadcrumbs/BreadcrumbItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:BreadcrumbItem +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Breadcrumbs/Breadcrumbs.spec.md b/packages/core/src/Breadcrumbs/Breadcrumbs.spec.md index 8b779c985ca3d..f462bf2e4fd17 100644 --- a/packages/core/src/Breadcrumbs/Breadcrumbs.spec.md +++ b/packages/core/src/Breadcrumbs/Breadcrumbs.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Breadcrumbs +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Button/Button.spec.md b/packages/core/src/Button/Button.spec.md index d74820befb840..332ede4ce10c8 100644 --- a/packages/core/src/Button/Button.spec.md +++ b/packages/core/src/Button/Button.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Button +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/ButtonGroup/ButtonGroup.spec.md b/packages/core/src/ButtonGroup/ButtonGroup.spec.md index 7e7838f91ace6..c1fec11ba8454 100644 --- a/packages/core/src/ButtonGroup/ButtonGroup.spec.md +++ b/packages/core/src/ButtonGroup/ButtonGroup.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:ButtonGroup +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Calendar/Calendar.spec.md b/packages/core/src/Calendar/Calendar.spec.md index 0fc505646c77a..d4c76ecf7282b 100644 --- a/packages/core/src/Calendar/Calendar.spec.md +++ b/packages/core/src/Calendar/Calendar.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Calendar +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Carousel/Carousel.spec.md b/packages/core/src/Carousel/Carousel.spec.md index f697b8be00bb4..86d4a245b9d8c 100644 --- a/packages/core/src/Carousel/Carousel.spec.md +++ b/packages/core/src/Carousel/Carousel.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Carousel +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Center/Center.spec.md b/packages/core/src/Center/Center.spec.md index 33e8185050fb2..55e46f3802458 100644 --- a/packages/core/src/Center/Center.spec.md +++ b/packages/core/src/Center/Center.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:Center +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatComposer.spec.md b/packages/core/src/Chat/ChatComposer.spec.md index dfddd509a986a..b6b3a59c92455 100644 --- a/packages/core/src/Chat/ChatComposer.spec.md +++ b/packages/core/src/Chat/ChatComposer.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatComposer +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatComposerDrawer.spec.md b/packages/core/src/Chat/ChatComposerDrawer.spec.md index 4daf9fece6301..679292c9a7f38 100644 --- a/packages/core/src/Chat/ChatComposerDrawer.spec.md +++ b/packages/core/src/Chat/ChatComposerDrawer.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatComposerDrawer +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatComposerInput.spec.md b/packages/core/src/Chat/ChatComposerInput.spec.md index f4605009f40b2..ef96f3dea22cf 100644 --- a/packages/core/src/Chat/ChatComposerInput.spec.md +++ b/packages/core/src/Chat/ChatComposerInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatComposerInput +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatComposerTokenElement.spec.md b/packages/core/src/Chat/ChatComposerTokenElement.spec.md index 585fc107990e4..dd06f6c3b5979 100644 --- a/packages/core/src/Chat/ChatComposerTokenElement.spec.md +++ b/packages/core/src/Chat/ChatComposerTokenElement.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatComposerTokenElement +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatDictationButton.spec.md b/packages/core/src/Chat/ChatDictationButton.spec.md index f6305559af39e..0f45880f72974 100644 --- a/packages/core/src/Chat/ChatDictationButton.spec.md +++ b/packages/core/src/Chat/ChatDictationButton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatDictationButton +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatLayout.spec.md b/packages/core/src/Chat/ChatLayout.spec.md index 67b74405d6703..c937108994c02 100644 --- a/packages/core/src/Chat/ChatLayout.spec.md +++ b/packages/core/src/Chat/ChatLayout.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatLayout +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatLayoutScrollButton.spec.md b/packages/core/src/Chat/ChatLayoutScrollButton.spec.md index f50e722d6fbe9..a82680382d566 100644 --- a/packages/core/src/Chat/ChatLayoutScrollButton.spec.md +++ b/packages/core/src/Chat/ChatLayoutScrollButton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:ChatLayoutScrollButton +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatMessage.spec.md b/packages/core/src/Chat/ChatMessage.spec.md index 7d4bea5394ba8..d466ba35b7a31 100644 --- a/packages/core/src/Chat/ChatMessage.spec.md +++ b/packages/core/src/Chat/ChatMessage.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatMessage +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatMessageBubble.spec.md b/packages/core/src/Chat/ChatMessageBubble.spec.md index f0049f4bea99f..e0e7d62c6974c 100644 --- a/packages/core/src/Chat/ChatMessageBubble.spec.md +++ b/packages/core/src/Chat/ChatMessageBubble.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatMessageBubble +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatMessageList.spec.md b/packages/core/src/Chat/ChatMessageList.spec.md index 05401a8180ced..a02f19f0d9dfb 100644 --- a/packages/core/src/Chat/ChatMessageList.spec.md +++ b/packages/core/src/Chat/ChatMessageList.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatMessageList +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatMessageMetadata.spec.md b/packages/core/src/Chat/ChatMessageMetadata.spec.md index 497ee42a40fc7..56b43764ead94 100644 --- a/packages/core/src/Chat/ChatMessageMetadata.spec.md +++ b/packages/core/src/Chat/ChatMessageMetadata.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatMessageMetadata +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatSendButton.spec.md b/packages/core/src/Chat/ChatSendButton.spec.md index 240cccdce7a45..1c7033672099d 100644 --- a/packages/core/src/Chat/ChatSendButton.spec.md +++ b/packages/core/src/Chat/ChatSendButton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatSendButton +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatSystemMessage.spec.md b/packages/core/src/Chat/ChatSystemMessage.spec.md index f01950f752aca..19584168562dd 100644 --- a/packages/core/src/Chat/ChatSystemMessage.spec.md +++ b/packages/core/src/Chat/ChatSystemMessage.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatSystemMessage +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatTokenizedText.spec.md b/packages/core/src/Chat/ChatTokenizedText.spec.md index 2e8eb7a3e4820..b46749fa604c8 100644 --- a/packages/core/src/Chat/ChatTokenizedText.spec.md +++ b/packages/core/src/Chat/ChatTokenizedText.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatTokenizedText +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Chat/ChatToolCalls.spec.md b/packages/core/src/Chat/ChatToolCalls.spec.md index 0ce82a2f45825..17579a61388bf 100644 --- a/packages/core/src/Chat/ChatToolCalls.spec.md +++ b/packages/core/src/Chat/ChatToolCalls.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ChatToolCalls +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CheckboxInput/CheckboxInput.spec.md b/packages/core/src/CheckboxInput/CheckboxInput.spec.md index 7dd9b8802354c..67edf422aaecc 100644 --- a/packages/core/src/CheckboxInput/CheckboxInput.spec.md +++ b/packages/core/src/CheckboxInput/CheckboxInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:CheckboxInput +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CheckboxList/CheckboxList.spec.md b/packages/core/src/CheckboxList/CheckboxList.spec.md index 0920595da9f13..dee18b8230d73 100644 --- a/packages/core/src/CheckboxList/CheckboxList.spec.md +++ b/packages/core/src/CheckboxList/CheckboxList.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:CheckboxList +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CheckboxList/CheckboxListItem.spec.md b/packages/core/src/CheckboxList/CheckboxListItem.spec.md index e7ced972a0b8b..6c13a9edcde14 100644 --- a/packages/core/src/CheckboxList/CheckboxListItem.spec.md +++ b/packages/core/src/CheckboxList/CheckboxListItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:CheckboxListItem +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Citation/Citation.spec.md b/packages/core/src/Citation/Citation.spec.md index a0a6f2ba2d023..f3a6a3509566e 100644 --- a/packages/core/src/Citation/Citation.spec.md +++ b/packages/core/src/Citation/Citation.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:Citation +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/ClickableCard/ClickableCard.spec.md b/packages/core/src/ClickableCard/ClickableCard.spec.md index f24bcfde85c61..c7947948c0394 100644 --- a/packages/core/src/ClickableCard/ClickableCard.spec.md +++ b/packages/core/src/ClickableCard/ClickableCard.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:ClickableCard +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Code/Code.spec.md b/packages/core/src/Code/Code.spec.md index 19c834a757fb4..cc3295e36e694 100644 --- a/packages/core/src/Code/Code.spec.md +++ b/packages/core/src/Code/Code.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Code +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/CodeBlock/CodeBlock.spec.md b/packages/core/src/CodeBlock/CodeBlock.spec.md index b7c0992e58a1c..4406b784adfe2 100644 --- a/packages/core/src/CodeBlock/CodeBlock.spec.md +++ b/packages/core/src/CodeBlock/CodeBlock.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CodeBlock +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Collapsible/Collapsible.spec.md b/packages/core/src/Collapsible/Collapsible.spec.md index 79a6a6061d87c..628a4b49be413 100644 --- a/packages/core/src/Collapsible/Collapsible.spec.md +++ b/packages/core/src/Collapsible/Collapsible.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:Collapsible +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Collapsible/CollapsibleGroup.spec.md b/packages/core/src/Collapsible/CollapsibleGroup.spec.md index bf3f8525bb7d6..7242a5e5c2937 100644 --- a/packages/core/src/Collapsible/CollapsibleGroup.spec.md +++ b/packages/core/src/Collapsible/CollapsibleGroup.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CollapsibleGroup +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CommandPalette/CommandPalette.spec.md b/packages/core/src/CommandPalette/CommandPalette.spec.md index 4dff2ab53191c..5b9b131d19d2d 100644 --- a/packages/core/src/CommandPalette/CommandPalette.spec.md +++ b/packages/core/src/CommandPalette/CommandPalette.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CommandPalette +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CommandPalette/CommandPaletteEmpty.spec.md b/packages/core/src/CommandPalette/CommandPaletteEmpty.spec.md index cd324b8612c19..10e3a290ecf32 100644 --- a/packages/core/src/CommandPalette/CommandPaletteEmpty.spec.md +++ b/packages/core/src/CommandPalette/CommandPaletteEmpty.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CommandPaletteEmpty +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CommandPalette/CommandPaletteInput.spec.md b/packages/core/src/CommandPalette/CommandPaletteInput.spec.md index 341523bd29bb7..5337b028748e6 100644 --- a/packages/core/src/CommandPalette/CommandPaletteInput.spec.md +++ b/packages/core/src/CommandPalette/CommandPaletteInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CommandPaletteInput +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/CommandPalette/CommandPaletteItem.spec.md b/packages/core/src/CommandPalette/CommandPaletteItem.spec.md index 45e435538b581..ba427832c5235 100644 --- a/packages/core/src/CommandPalette/CommandPaletteItem.spec.md +++ b/packages/core/src/CommandPalette/CommandPaletteItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CommandPaletteItem +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/CommandPalette/CommandPaletteList.spec.md b/packages/core/src/CommandPalette/CommandPaletteList.spec.md index 1e6920770af82..5e690b393758c 100644 --- a/packages/core/src/CommandPalette/CommandPaletteList.spec.md +++ b/packages/core/src/CommandPalette/CommandPaletteList.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CommandPaletteList +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/ComplexSelector/ComplexSelector.spec.md b/packages/core/src/ComplexSelector/ComplexSelector.spec.md index 07ecebcf21261..a860771843495 100644 --- a/packages/core/src/ComplexSelector/ComplexSelector.spec.md +++ b/packages/core/src/ComplexSelector/ComplexSelector.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:ComplexSelector +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/ContextMenu/ContextMenu.spec.md b/packages/core/src/ContextMenu/ContextMenu.spec.md index 9ad771e40db7f..dc619d8e74969 100644 --- a/packages/core/src/ContextMenu/ContextMenu.spec.md +++ b/packages/core/src/ContextMenu/ContextMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:ContextMenu +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DateInput/DateInput.spec.md b/packages/core/src/DateInput/DateInput.spec.md index 42c8b3a1f8530..9e1c119e8db8f 100644 --- a/packages/core/src/DateInput/DateInput.spec.md +++ b/packages/core/src/DateInput/DateInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DateInput +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DateRangeInput/DateRangeInput.spec.md b/packages/core/src/DateRangeInput/DateRangeInput.spec.md index 142dc7613b579..f0c739caab8a6 100644 --- a/packages/core/src/DateRangeInput/DateRangeInput.spec.md +++ b/packages/core/src/DateRangeInput/DateRangeInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:DateRangeInput +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/DateTimeInput/DateTimeInput.spec.md b/packages/core/src/DateTimeInput/DateTimeInput.spec.md index 929385d1d3ddf..e927bc419ef30 100644 --- a/packages/core/src/DateTimeInput/DateTimeInput.spec.md +++ b/packages/core/src/DateTimeInput/DateTimeInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DateTimeInput +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Dialog/Dialog.spec.md b/packages/core/src/Dialog/Dialog.spec.md index 22cea528cc65c..7945b21af8321 100644 --- a/packages/core/src/Dialog/Dialog.spec.md +++ b/packages/core/src/Dialog/Dialog.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:Dialog +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Divider/Divider.spec.md b/packages/core/src/Divider/Divider.spec.md index 78fbd25a15772..06c75fd7833fe 100644 --- a/packages/core/src/Divider/Divider.spec.md +++ b/packages/core/src/Divider/Divider.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Divider +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Drawer/Drawer.spec.md b/packages/core/src/Drawer/Drawer.spec.md index f3e20409a620a..dfe6e06d7e8f3 100644 --- a/packages/core/src/Drawer/Drawer.spec.md +++ b/packages/core/src/Drawer/Drawer.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:Drawer +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DropdownMenu/DropdownMenu.spec.md b/packages/core/src/DropdownMenu/DropdownMenu.spec.md index d1091b9a15444..553fb945125f8 100644 --- a/packages/core/src/DropdownMenu/DropdownMenu.spec.md +++ b/packages/core/src/DropdownMenu/DropdownMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:DropdownMenu +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DropdownMenu/DropdownMenuDivider.spec.md b/packages/core/src/DropdownMenu/DropdownMenuDivider.spec.md index c7fc5a2cb0663..840081bec9736 100644 --- a/packages/core/src/DropdownMenu/DropdownMenuDivider.spec.md +++ b/packages/core/src/DropdownMenu/DropdownMenuDivider.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DropdownMenuDivider +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DropdownMenu/DropdownMenuItem.spec.md b/packages/core/src/DropdownMenu/DropdownMenuItem.spec.md index d229cdf8dffe4..fd45f1734d962 100644 --- a/packages/core/src/DropdownMenu/DropdownMenuItem.spec.md +++ b/packages/core/src/DropdownMenu/DropdownMenuItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DropdownMenuItem +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DropdownMenu/DropdownMenuRadioGroup.spec.md b/packages/core/src/DropdownMenu/DropdownMenuRadioGroup.spec.md index 178dda62e2a64..1fa6747fd9ade 100644 --- a/packages/core/src/DropdownMenu/DropdownMenuRadioGroup.spec.md +++ b/packages/core/src/DropdownMenu/DropdownMenuRadioGroup.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DropdownMenuRadioGroup +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DropdownMenu/DropdownMenuRadioItem.spec.md b/packages/core/src/DropdownMenu/DropdownMenuRadioItem.spec.md index 1f8dbff77d38e..2d537288a5a97 100644 --- a/packages/core/src/DropdownMenu/DropdownMenuRadioItem.spec.md +++ b/packages/core/src/DropdownMenu/DropdownMenuRadioItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DropdownMenuRadioItem +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/DropdownMenu/DropdownMenuSubMenu.spec.md b/packages/core/src/DropdownMenu/DropdownMenuSubMenu.spec.md index 08d0562a97dc6..07b7d86f9682d 100644 --- a/packages/core/src/DropdownMenu/DropdownMenuSubMenu.spec.md +++ b/packages/core/src/DropdownMenu/DropdownMenuSubMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:DropdownMenuSubMenu +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/EmptyState/EmptyState.spec.md b/packages/core/src/EmptyState/EmptyState.spec.md index 2dad2a7c2d6ce..37fb2eac9fc0e 100644 --- a/packages/core/src/EmptyState/EmptyState.spec.md +++ b/packages/core/src/EmptyState/EmptyState.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:EmptyState +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Field/Field.spec.md b/packages/core/src/Field/Field.spec.md index 6dfcd16fb7917..6b6b3def244a2 100644 --- a/packages/core/src/Field/Field.spec.md +++ b/packages/core/src/Field/Field.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Field +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Field/FieldLabel.spec.md b/packages/core/src/Field/FieldLabel.spec.md index 286ee234c7c8a..5633c888c6fbd 100644 --- a/packages/core/src/Field/FieldLabel.spec.md +++ b/packages/core/src/Field/FieldLabel.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:FieldLabel +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/FieldStatus/FieldStatus.spec.md b/packages/core/src/FieldStatus/FieldStatus.spec.md index afba3e860faf6..c6f375ba2a006 100644 --- a/packages/core/src/FieldStatus/FieldStatus.spec.md +++ b/packages/core/src/FieldStatus/FieldStatus.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 1 kind: component id: component:FieldStatus +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/FileInput/FileInput.spec.md b/packages/core/src/FileInput/FileInput.spec.md index 1a6bbd4db7bbe..137c47f20157d 100644 --- a/packages/core/src/FileInput/FileInput.spec.md +++ b/packages/core/src/FileInput/FileInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:FileInput +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/FormLayout/FormLayout.spec.md b/packages/core/src/FormLayout/FormLayout.spec.md index bf27f53df8e69..8978227867111 100644 --- a/packages/core/src/FormLayout/FormLayout.spec.md +++ b/packages/core/src/FormLayout/FormLayout.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:FormLayout +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Grid/Grid.spec.md b/packages/core/src/Grid/Grid.spec.md index 9136dcc210703..b81217b8e5984 100644 --- a/packages/core/src/Grid/Grid.spec.md +++ b/packages/core/src/Grid/Grid.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Grid +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Icon/Icon.spec.md b/packages/core/src/Icon/Icon.spec.md index 83d20a6c0b90e..d962b4f82efce 100644 --- a/packages/core/src/Icon/Icon.spec.md +++ b/packages/core/src/Icon/Icon.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Icon +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/IconButton/IconButton.spec.md b/packages/core/src/IconButton/IconButton.spec.md index 6d1928268ea3b..11187b8728a2e 100644 --- a/packages/core/src/IconButton/IconButton.spec.md +++ b/packages/core/src/IconButton/IconButton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:IconButton +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Indicator/CheckIndicator.spec.md b/packages/core/src/Indicator/CheckIndicator.spec.md index 5d099ed54c8b5..d248443e403b7 100644 --- a/packages/core/src/Indicator/CheckIndicator.spec.md +++ b/packages/core/src/Indicator/CheckIndicator.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:CheckIndicator +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Indicator/CheckboxIndicator.spec.md b/packages/core/src/Indicator/CheckboxIndicator.spec.md index 76fbdda383c7d..36f1791f2ebaa 100644 --- a/packages/core/src/Indicator/CheckboxIndicator.spec.md +++ b/packages/core/src/Indicator/CheckboxIndicator.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 6 kind: component id: component:CheckboxIndicator +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Item/Item.spec.md b/packages/core/src/Item/Item.spec.md index 5077c1af80811..16065ab59861d 100644 --- a/packages/core/src/Item/Item.spec.md +++ b/packages/core/src/Item/Item.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Item +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Kbd/Kbd.spec.md b/packages/core/src/Kbd/Kbd.spec.md index f2741b6521991..da1e4c68b9532 100644 --- a/packages/core/src/Kbd/Kbd.spec.md +++ b/packages/core/src/Kbd/Kbd.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Kbd +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Layout/Layout.spec.md b/packages/core/src/Layout/Layout.spec.md index 6a0c5f299df56..c6e7d558531d3 100644 --- a/packages/core/src/Layout/Layout.spec.md +++ b/packages/core/src/Layout/Layout.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Layout +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Lightbox/Lightbox.spec.md b/packages/core/src/Lightbox/Lightbox.spec.md index 4be20e55ecac8..4413ec4631f1d 100644 --- a/packages/core/src/Lightbox/Lightbox.spec.md +++ b/packages/core/src/Lightbox/Lightbox.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Lightbox +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Link/Link.spec.md b/packages/core/src/Link/Link.spec.md index dd0f25242fac9..2148f7a63fe8e 100644 --- a/packages/core/src/Link/Link.spec.md +++ b/packages/core/src/Link/Link.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Link +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Link/LinkProvider.spec.md b/packages/core/src/Link/LinkProvider.spec.md index bc945f81f3659..3cd5632371d52 100644 --- a/packages/core/src/Link/LinkProvider.spec.md +++ b/packages/core/src/Link/LinkProvider.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:LinkProvider +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/List/List.spec.md b/packages/core/src/List/List.spec.md index 032095a5b9c38..0e4b05624bb8f 100644 --- a/packages/core/src/List/List.spec.md +++ b/packages/core/src/List/List.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:List +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Markdown/Markdown.spec.md b/packages/core/src/Markdown/Markdown.spec.md index c0e2a11327800..41044c0585b12 100644 --- a/packages/core/src/Markdown/Markdown.spec.md +++ b/packages/core/src/Markdown/Markdown.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Markdown +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/MobileNav/MobileNav.spec.md b/packages/core/src/MobileNav/MobileNav.spec.md index 1fd887d17084d..f4b1c23d5e9ab 100644 --- a/packages/core/src/MobileNav/MobileNav.spec.md +++ b/packages/core/src/MobileNav/MobileNav.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:MobileNav +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/MoreMenu/MoreMenu.spec.md b/packages/core/src/MoreMenu/MoreMenu.spec.md index 9947098871c62..f92fd3660b0bf 100644 --- a/packages/core/src/MoreMenu/MoreMenu.spec.md +++ b/packages/core/src/MoreMenu/MoreMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:MoreMenu +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/MultiSelector/MultiSelector.spec.md b/packages/core/src/MultiSelector/MultiSelector.spec.md index d7090ce69469e..24f05453f6741 100644 --- a/packages/core/src/MultiSelector/MultiSelector.spec.md +++ b/packages/core/src/MultiSelector/MultiSelector.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:MultiSelector +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/NavIcon/NavIcon.spec.md b/packages/core/src/NavIcon/NavIcon.spec.md index f7afeb7e8e424..d961e5e552bd9 100644 --- a/packages/core/src/NavIcon/NavIcon.spec.md +++ b/packages/core/src/NavIcon/NavIcon.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:NavIcon +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/NavMenu/NavHeadingMenu.spec.md b/packages/core/src/NavMenu/NavHeadingMenu.spec.md index afe042d0f02e6..bb0ac53a71bae 100644 --- a/packages/core/src/NavMenu/NavHeadingMenu.spec.md +++ b/packages/core/src/NavMenu/NavHeadingMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:NavHeadingMenu +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/NumberInput/NumberInput.spec.md b/packages/core/src/NumberInput/NumberInput.spec.md index 8c187c5d4c41c..55c7e82c75ac6 100644 --- a/packages/core/src/NumberInput/NumberInput.spec.md +++ b/packages/core/src/NumberInput/NumberInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:NumberInput +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Outline/Outline.spec.md b/packages/core/src/Outline/Outline.spec.md index 2626487ccd7df..7535b0f7cae18 100644 --- a/packages/core/src/Outline/Outline.spec.md +++ b/packages/core/src/Outline/Outline.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Outline +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/OverflowList/OverflowList.spec.md b/packages/core/src/OverflowList/OverflowList.spec.md index d6a99ccce9037..19d7338b585c3 100644 --- a/packages/core/src/OverflowList/OverflowList.spec.md +++ b/packages/core/src/OverflowList/OverflowList.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:OverflowList +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Overlay/Overlay.spec.md b/packages/core/src/Overlay/Overlay.spec.md index 075f73508bc5a..ffeb879c9a8d9 100644 --- a/packages/core/src/Overlay/Overlay.spec.md +++ b/packages/core/src/Overlay/Overlay.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Overlay +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Pagination/Pagination.spec.md b/packages/core/src/Pagination/Pagination.spec.md index 9335ae6aba197..cc49aa1dbbe74 100644 --- a/packages/core/src/Pagination/Pagination.spec.md +++ b/packages/core/src/Pagination/Pagination.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Pagination +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Popover/Popover.spec.md b/packages/core/src/Popover/Popover.spec.md index 6fcdf88849bfe..19b5af173a264 100644 --- a/packages/core/src/Popover/Popover.spec.md +++ b/packages/core/src/Popover/Popover.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Popover +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/PowerSearch/PowerSearch.spec.md b/packages/core/src/PowerSearch/PowerSearch.spec.md index e665ffa5e667c..483c997da5541 100644 --- a/packages/core/src/PowerSearch/PowerSearch.spec.md +++ b/packages/core/src/PowerSearch/PowerSearch.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:PowerSearch +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/ProgressBar/ProgressBar.spec.md b/packages/core/src/ProgressBar/ProgressBar.spec.md index 391009c1b0df6..5a073dc57f2cd 100644 --- a/packages/core/src/ProgressBar/ProgressBar.spec.md +++ b/packages/core/src/ProgressBar/ProgressBar.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:ProgressBar +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Resizable/Resizable.spec.md b/packages/core/src/Resizable/Resizable.spec.md index 04abed4450d67..1775117e3da48 100644 --- a/packages/core/src/Resizable/Resizable.spec.md +++ b/packages/core/src/Resizable/Resizable.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Resizable +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/ScrollableArea/ScrollableArea.spec.md b/packages/core/src/ScrollableArea/ScrollableArea.spec.md index bb4fe4499e8d2..b7dcb9cb71200 100644 --- a/packages/core/src/ScrollableArea/ScrollableArea.spec.md +++ b/packages/core/src/ScrollableArea/ScrollableArea.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:ScrollableArea +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Section/Section.spec.md b/packages/core/src/Section/Section.spec.md index a331608fccf3a..bf43e10a0e6a9 100644 --- a/packages/core/src/Section/Section.spec.md +++ b/packages/core/src/Section/Section.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Section +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/SegmentedControl/SegmentedControl.spec.md b/packages/core/src/SegmentedControl/SegmentedControl.spec.md index 6a3a4c243f858..cdffdbe31b971 100644 --- a/packages/core/src/SegmentedControl/SegmentedControl.spec.md +++ b/packages/core/src/SegmentedControl/SegmentedControl.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:SegmentedControl +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Selector/Selector.spec.md b/packages/core/src/Selector/Selector.spec.md index a2e5724a39403..7e704fe595a21 100644 --- a/packages/core/src/Selector/Selector.spec.md +++ b/packages/core/src/Selector/Selector.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Selector +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/SideNav/SideNav.spec.md b/packages/core/src/SideNav/SideNav.spec.md index c959707ccb9ef..3861d8ab541fd 100644 --- a/packages/core/src/SideNav/SideNav.spec.md +++ b/packages/core/src/SideNav/SideNav.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:SideNav +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/SideNav/SideNavCollapseButton.spec.md b/packages/core/src/SideNav/SideNavCollapseButton.spec.md index 3ae1f72f2c5f6..300fa3ccdf996 100644 --- a/packages/core/src/SideNav/SideNavCollapseButton.spec.md +++ b/packages/core/src/SideNav/SideNavCollapseButton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:SideNavCollapseButton +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/SideNav/SideNavHeading.spec.md b/packages/core/src/SideNav/SideNavHeading.spec.md index 2bfea03eb36dc..3de2735c999db 100644 --- a/packages/core/src/SideNav/SideNavHeading.spec.md +++ b/packages/core/src/SideNav/SideNavHeading.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:SideNavHeading +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/SideNav/SideNavItem.spec.md b/packages/core/src/SideNav/SideNavItem.spec.md index dd925aedc534e..199a95c88cc67 100644 --- a/packages/core/src/SideNav/SideNavItem.spec.md +++ b/packages/core/src/SideNav/SideNavItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:SideNavItem +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/SideNav/SideNavSection.spec.md b/packages/core/src/SideNav/SideNavSection.spec.md index 8785e4c36d10b..547571290f3b1 100644 --- a/packages/core/src/SideNav/SideNavSection.spec.md +++ b/packages/core/src/SideNav/SideNavSection.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:SideNavSection +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Skeleton/Skeleton.spec.md b/packages/core/src/Skeleton/Skeleton.spec.md index 141bd3e665ebd..821a1f4e90736 100644 --- a/packages/core/src/Skeleton/Skeleton.spec.md +++ b/packages/core/src/Skeleton/Skeleton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Skeleton +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Slider/Slider.spec.md b/packages/core/src/Slider/Slider.spec.md index 17552e5d0777f..127f261a17b14 100644 --- a/packages/core/src/Slider/Slider.spec.md +++ b/packages/core/src/Slider/Slider.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:Slider +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Spinner/Spinner.spec.md b/packages/core/src/Spinner/Spinner.spec.md index 9c7bbfe6684dc..2ef9e1f16d76c 100644 --- a/packages/core/src/Spinner/Spinner.spec.md +++ b/packages/core/src/Spinner/Spinner.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Spinner +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Stack/Stack.spec.md b/packages/core/src/Stack/Stack.spec.md index 36feb8cfcb219..0cf95a4c656c0 100644 --- a/packages/core/src/Stack/Stack.spec.md +++ b/packages/core/src/Stack/Stack.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Stack +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/StatusDot/StatusDot.spec.md b/packages/core/src/StatusDot/StatusDot.spec.md index 7b2414b4e5578..dfb6814019781 100644 --- a/packages/core/src/StatusDot/StatusDot.spec.md +++ b/packages/core/src/StatusDot/StatusDot.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:StatusDot +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Stepper/Stepper.spec.md b/packages/core/src/Stepper/Stepper.spec.md index e1a17519ab503..088b2423ba213 100644 --- a/packages/core/src/Stepper/Stepper.spec.md +++ b/packages/core/src/Stepper/Stepper.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Stepper +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Switch/Switch.spec.md b/packages/core/src/Switch/Switch.spec.md index 6fae3f77e8f8d..fb4ee3a67400c 100644 --- a/packages/core/src/Switch/Switch.spec.md +++ b/packages/core/src/Switch/Switch.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Switch +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/TabList/TabList.spec.md b/packages/core/src/TabList/TabList.spec.md index 02b0c34070a8d..3f770e85f1e44 100644 --- a/packages/core/src/TabList/TabList.spec.md +++ b/packages/core/src/TabList/TabList.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:TabList +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Table/Table.spec.md b/packages/core/src/Table/Table.spec.md index 02495d4cbfeea..80f8e8f626ec8 100644 --- a/packages/core/src/Table/Table.spec.md +++ b/packages/core/src/Table/Table.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Table +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Text/Text.spec.md b/packages/core/src/Text/Text.spec.md index 48485c1547406..a5162634f73d2 100644 --- a/packages/core/src/Text/Text.spec.md +++ b/packages/core/src/Text/Text.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Text +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/TextArea/TextArea.spec.md b/packages/core/src/TextArea/TextArea.spec.md index 9885d0d409ea2..61ee7a269b176 100644 --- a/packages/core/src/TextArea/TextArea.spec.md +++ b/packages/core/src/TextArea/TextArea.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:TextArea +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/TextInput/TextInput.spec.md b/packages/core/src/TextInput/TextInput.spec.md index ac60ab74b2ce3..8e65cc3410396 100644 --- a/packages/core/src/TextInput/TextInput.spec.md +++ b/packages/core/src/TextInput/TextInput.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TextInput +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Thumbnail/Thumbnail.spec.md b/packages/core/src/Thumbnail/Thumbnail.spec.md index 6546fd3469a65..fef10d7c389ba 100644 --- a/packages/core/src/Thumbnail/Thumbnail.spec.md +++ b/packages/core/src/Thumbnail/Thumbnail.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Thumbnail +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Timer/Timer.spec.md b/packages/core/src/Timer/Timer.spec.md index a52d290b7b90a..742d9dc35b914 100644 --- a/packages/core/src/Timer/Timer.spec.md +++ b/packages/core/src/Timer/Timer.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:Timer +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/ToggleButton/ToggleButton.spec.md b/packages/core/src/ToggleButton/ToggleButton.spec.md index d20331eadc19c..7c332cd016bea 100644 --- a/packages/core/src/ToggleButton/ToggleButton.spec.md +++ b/packages/core/src/ToggleButton/ToggleButton.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:ToggleButton +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/ToggleButton/ToggleButtonGroup.spec.md b/packages/core/src/ToggleButton/ToggleButtonGroup.spec.md index da31357296411..a5ab60a9ed763 100644 --- a/packages/core/src/ToggleButton/ToggleButtonGroup.spec.md +++ b/packages/core/src/ToggleButton/ToggleButtonGroup.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:ToggleButtonGroup +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Token/Token.spec.md b/packages/core/src/Token/Token.spec.md index 6b7b454def735..2777ea09e6f16 100644 --- a/packages/core/src/Token/Token.spec.md +++ b/packages/core/src/Token/Token.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Token +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Tokenizer/Tokenizer.spec.md b/packages/core/src/Tokenizer/Tokenizer.spec.md index 4bad54c7e2306..96cf05d2234aa 100644 --- a/packages/core/src/Tokenizer/Tokenizer.spec.md +++ b/packages/core/src/Tokenizer/Tokenizer.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Tokenizer +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Toolbar/Toolbar.spec.md b/packages/core/src/Toolbar/Toolbar.spec.md index eeedcd1c58e9c..9e53e572447cb 100644 --- a/packages/core/src/Toolbar/Toolbar.spec.md +++ b/packages/core/src/Toolbar/Toolbar.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Toolbar +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Tooltip/Tooltip.spec.md b/packages/core/src/Tooltip/Tooltip.spec.md index 22d5fab9eb31f..7ebe325e5ab7d 100644 --- a/packages/core/src/Tooltip/Tooltip.spec.md +++ b/packages/core/src/Tooltip/Tooltip.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Tooltip +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNav.spec.md b/packages/core/src/TopNav/TopNav.spec.md index 84b2ca4fab7a3..c2082253c25fb 100644 --- a/packages/core/src/TopNav/TopNav.spec.md +++ b/packages/core/src/TopNav/TopNav.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNav +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNavHeading.spec.md b/packages/core/src/TopNav/TopNavHeading.spec.md index 4e6e4727e55aa..da995acacb84c 100644 --- a/packages/core/src/TopNav/TopNavHeading.spec.md +++ b/packages/core/src/TopNav/TopNavHeading.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNavHeading +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNavItem.spec.md b/packages/core/src/TopNav/TopNavItem.spec.md index 4c407084b051a..c65f24f80a91f 100644 --- a/packages/core/src/TopNav/TopNavItem.spec.md +++ b/packages/core/src/TopNav/TopNavItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNavItem +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNavMegaMenu.spec.md b/packages/core/src/TopNav/TopNavMegaMenu.spec.md index 27b35de682c56..2bb78c02e925c 100644 --- a/packages/core/src/TopNav/TopNavMegaMenu.spec.md +++ b/packages/core/src/TopNav/TopNavMegaMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNavMegaMenu +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNavMegaMenuFeaturedCard.spec.md b/packages/core/src/TopNav/TopNavMegaMenuFeaturedCard.spec.md index 6ae5dee92fe36..4fd0027c7b2d3 100644 --- a/packages/core/src/TopNav/TopNavMegaMenuFeaturedCard.spec.md +++ b/packages/core/src/TopNav/TopNavMegaMenuFeaturedCard.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNavMegaMenuFeaturedCard +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNavMegaMenuItem.spec.md b/packages/core/src/TopNav/TopNavMegaMenuItem.spec.md index f5305197238d6..826acc60cde93 100644 --- a/packages/core/src/TopNav/TopNavMegaMenuItem.spec.md +++ b/packages/core/src/TopNav/TopNavMegaMenuItem.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNavMegaMenuItem +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TopNav/TopNavMenu.spec.md b/packages/core/src/TopNav/TopNavMenu.spec.md index 14bc1305a6e86..80680b460e7d8 100644 --- a/packages/core/src/TopNav/TopNavMenu.spec.md +++ b/packages/core/src/TopNav/TopNavMenu.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:TopNavMenu +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/TreeList/TreeList.spec.md b/packages/core/src/TreeList/TreeList.spec.md index 1990e73f7ca58..a5e9c3ac73d5e 100644 --- a/packages/core/src/TreeList/TreeList.spec.md +++ b/packages/core/src/TreeList/TreeList.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 5 kind: component id: component:TreeList +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/core/src/Typeahead/BaseTypeahead.spec.md b/packages/core/src/Typeahead/BaseTypeahead.spec.md index 538f555bcb4ce..2d8a9c6cd043b 100644 --- a/packages/core/src/Typeahead/BaseTypeahead.spec.md +++ b/packages/core/src/Typeahead/BaseTypeahead.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 4 kind: component id: component:BaseTypeahead +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/Typeahead/Typeahead.spec.md b/packages/core/src/Typeahead/Typeahead.spec.md index 50ae4ee2425af..0f038435d4543 100644 --- a/packages/core/src/Typeahead/Typeahead.spec.md +++ b/packages/core/src/Typeahead/Typeahead.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 3 kind: component id: component:Typeahead +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/core/src/i18n/InternationalizationProvider.spec.md b/packages/core/src/i18n/InternationalizationProvider.spec.md index 57deef039803a..02abbc70ace8b 100644 --- a/packages/core/src/i18n/InternationalizationProvider.spec.md +++ b/packages/core/src/i18n/InternationalizationProvider.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:InternationalizationProvider +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/lab/src/CommentPin/CommentPin.spec.md b/packages/lab/src/CommentPin/CommentPin.spec.md index 96ad0e0b74609..338f588cd9006 100644 --- a/packages/lab/src/CommentPin/CommentPin.spec.md +++ b/packages/lab/src/CommentPin/CommentPin.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:CommentPin +platform_of: null authority: draft archive_reason: null superseded_by: null diff --git a/packages/lab/src/Schedule/Schedule.spec.md b/packages/lab/src/Schedule/Schedule.spec.md index ad51be54a9343..f622f6c67647c 100644 --- a/packages/lab/src/Schedule/Schedule.spec.md +++ b/packages/lab/src/Schedule/Schedule.spec.md @@ -1,8 +1,9 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:Schedule +platform_of: null authority: current archive_reason: null superseded_by: null diff --git a/packages/native/apple/README.md b/packages/native/apple/README.md index 4a36fa92dc7bd..d2693674bdea5 100644 --- a/packages/native/apple/README.md +++ b/packages/native/apple/README.md @@ -123,7 +123,7 @@ the device has wins, and a system family such as `-apple-system` or one with `Theme(…, icons: ["success": "checkmark.seal.fill"])`. `Sources/Astryx/Theme/Generated/` is written by the Swift theme generator -(`../specs/swift/library/theme-generator.md`) from core's own resolver, light +(`architecture:native-swift-theme-generator`, `../specs/swift/theme-generator.spec.md`) from core's own resolver, light and dark: - `Tokens.swift`: the typed token surface, from core's token names. diff --git a/packages/native/apple/Sources/Astryx/Avatar/Avatar.spec.md b/packages/native/apple/Sources/Astryx/Avatar/Avatar.spec.md index a4880cf9f1055..e5f913b688e03 100644 --- a/packages/native/apple/Sources/Astryx/Avatar/Avatar.spec.md +++ b/packages/native/apple/Sources/Astryx/Avatar/Avatar.spec.md @@ -1,5 +1,5 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:swift/Avatar @@ -38,7 +38,7 @@ system_specs: [spec:AST-082, spec:AST-093] | Builder impact | An app passes a name and an image URL, and optionally tooltip words and an `AvatarStatusDot`. Pixel sizes, `target`, and `rel` have no Swift form; an `href` must be an absolute URL. | | Compatibility/readiness | Additive: a new view and generated `AvatarProps`. Draft; built and tested on macOS and the iOS Simulator; device captures are attached to the change. | | Review checks | Reject a size or shape beyond the contract, a disabled state, a link or button that draws differently from the static avatar, a layout that grows for the touch target, or a static avatar VoiceOver reads without a name. | -| Governing rules | `spec:AST-082` FR3, FR4, FR7; `spec:AST-093` FR1–FR5; the Swift component API (A1–A9), theme environment, and accessibility records under `packages/native/specs/swift/library/`. | +| Governing rules | `spec:AST-082` FR3, FR4, FR7; `spec:AST-093` FR1–FR5; the Swift component API (A1–A9), theme environment, and accessibility records under `packages/native/specs/swift/`. | This table is a review projection; the body below is authoritative. @@ -93,7 +93,7 @@ Consumer migration instructions belong in consumer docs and release notes. | FR5 | With an `href` that is an absolute URL (it has a scheme), the avatar MUST be a SwiftUI `Link`; with `onClick` too, `onClick` runs as it opens and the URL then goes to the app's own URL handler, or the system's. An `href` that is not an absolute URL, such as a web path, MUST stop a development build with a message naming it; a release build draws the avatar without a link, as a button with `onClick` or static. With only `onClick`, a SwiftUI `Button`. Either draws exactly as the static avatar, and the avatar has no disabled state. | Swift component API A4 | verify | | FR6 | Both initializers MUST build the same `AvatarProps`; `name` is the unlabeled first parameter, a single trailing closure is the status, and `onClick` comes last. Base props come through `AvatarProps`: with `isHidden` the view draws nothing; `id` becomes the accessibility identifier. | Swift component API A2–A4, A7; `spec:AST-082` FR7 | verify | | FR7 | The `status` slot MUST draw its content, usually one `AvatarStatusDot`, outside the avatar's clip, placed from the content's own size, in the same layout pass, as core places any status: on a circle centered on the circle's edge at the bottom trailing 45° point, on a rounded or square avatar overhanging the bottom trailing corner by a quarter of its size, mirrored in right-to-left layouts. It MUST NOT change the avatar's size or layout, gives a dot the avatar's side for its size tier, and lets other content grow with Dynamic Type no faster than the avatar, up to the text size at its 1.6 times growth: the content still grows with Dynamic Type (`spec:AST-082` FR11), up to its owner's growth, since the owner sizes what it holds (Swift slots S4). | core `Avatar` status position; Swift slots S4 | verify | -| FR8 | `tooltip` MUST resolve, as core does, to the name for `nil` or `true`, the given words for a string, and nothing for `false` or blank words, and MUST show through the Astryx tooltip (`architecture:native-swift-library-tooltip`), with the focus and touch the Tooltip by input table below states: a static avatar is a tab stop and opens on a tap; a link or button keeps its focus and its tap. Given words are the VoiceOver hint. On the wire it is the web value, a string or a Bool. | core `Avatar` `tooltip`; core `Tooltip`; `spec:AST-082` FR3 | verify | +| FR8 | `tooltip` MUST resolve, as core does, to the name for `nil` or `true`, the given words for a string, and nothing for `false` or blank words, and MUST show through the Astryx tooltip (`architecture:native-swift-tooltip`), with the focus and touch the Tooltip by input table below states: a static avatar is a tab stop and opens on a tap; a link or button keeps its focus and its tap. Given words are the VoiceOver hint. On the wire it is the web value, a string or a Bool. | core `Avatar` `tooltip`; core `Tooltip`; `spec:AST-082` FR3 | verify | **Tooltip by input (FR8).** The Astryx tooltip's rules apply; this table states Avatar's choices. diff --git a/packages/native/apple/Sources/Astryx/Avatar/Avatar.swift b/packages/native/apple/Sources/Astryx/Avatar/Avatar.swift index 40404ab856f2a..89d2b0a84520c 100644 --- a/packages/native/apple/Sources/Astryx/Avatar/Avatar.swift +++ b/packages/native/apple/Sources/Astryx/Avatar/Avatar.swift @@ -62,7 +62,7 @@ public struct Avatar: View { public var body: some View { if props.base.isHidden != true { control - // The Astryx tooltip (library/tooltip.spec.md), as core's Avatar uses + // The Astryx tooltip (specs/swift/tooltip.spec.md), as core's Avatar uses // core's tooltip: a static avatar is a tab stop and opens on a tap; // a link or button keeps its focus and its tap. // Touch is core's `auto`: a static avatar opens on a tap; a control's diff --git a/packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.spec.md b/packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.spec.md index c25f0fceab2d0..d1dc6d3d229bd 100644 --- a/packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.spec.md +++ b/packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.spec.md @@ -1,5 +1,5 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:swift/AvatarStatusDot @@ -36,7 +36,7 @@ system_specs: [spec:AST-082, spec:AST-093] | Builder impact | An app puts the dot in the avatar's trailing closure with a label. A custom icon inside the dot has no Swift form. | | Compatibility/readiness | Additive: a new view, generated `AvatarStatusDotProps`, and core's name-with-status message in the Swift string catalogs. Draft; built and tested on macOS and the iOS Simulator; device captures are attached. | | Review checks | Reject a status shown by color alone, a dot that resizes or moves the avatar, a dot that VoiceOver reads separately inside an avatar, or a color that is not a theme token. | -| Governing rules | `spec:AST-082` FR4, FR7; `spec:AST-093` FR1–FR5; the Swift component API, slots (S4), theme environment, and accessibility (X1, X6) records under `packages/native/specs/swift/library/`. | +| Governing rules | `spec:AST-082` FR4, FR7; `spec:AST-093` FR1–FR5; the Swift component API, slots (S4), theme environment, and accessibility (X1, X6) records under `packages/native/specs/swift/`. | This table is a review projection; the body below is authoritative. diff --git a/packages/native/apple/Sources/Astryx/Icon/Icon.spec.md b/packages/native/apple/Sources/Astryx/Icon/Icon.spec.md index 1088bf04bc093..c6588f1e53176 100644 --- a/packages/native/apple/Sources/Astryx/Icon/Icon.spec.md +++ b/packages/native/apple/Sources/Astryx/Icon/Icon.spec.md @@ -1,5 +1,5 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:swift/Icon @@ -40,7 +40,7 @@ system_specs: [spec:AST-082, spec:AST-093] | Builder impact | An app passes a shared name (`.search`) or a name it maps in its theme (`"acme:rocket"`). An SVG component, which the web also accepts, has no Swift form. | | Compatibility/readiness | Additive: a new view and a new generated `IconName` type. Draft; built and tested on macOS and the iOS Simulator; device captures are attached to the change. | | Review checks | Reject a substitute symbol for an unknown name, an image or attributed-text form of the icon slot, a visual parameter beyond the contract, an unlabeled icon that VoiceOver reads, or an owner's size winning over an explicit `size`. | -| Governing rules | `spec:AST-082` FR4, FR7, FR17; `spec:AST-093` FR1–FR5; the Swift component API, styles, slots, theme environment, and accessibility records under `packages/native/specs/swift/library/`. | +| Governing rules | `spec:AST-082` FR4, FR7, FR17; `spec:AST-093` FR1–FR5; the Swift component API, styles, slots, theme environment, and accessibility records under `packages/native/specs/swift/` (`architecture:native-swift-*`). | This table is a review projection; the body below is authoritative. diff --git a/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.spec.md b/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.spec.md index f4aa14c1bbe45..86666ab4d5761 100644 --- a/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.spec.md +++ b/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.spec.md @@ -1,5 +1,5 @@ --- -schema_version: 3 +schema_version: 5 template_version: 7 kind: component id: component:swift/Thumbnail @@ -37,7 +37,7 @@ system_specs: [spec:AST-082, spec:AST-093] | Builder impact | An app passes the image URL, a label such as a file name, and the actions it supports. A single trailing closure is `onClick`. | | Compatibility/readiness | Additive: a new view, generated `ThumbnailProps`, and four of core's messages in the Swift string catalogs. Draft; built and tested on macOS and the iOS Simulator; device captures are attached to the change. | | Review checks | Reject a hidden remove button on a touch device, a tap or remove that works while disabled, a tap that works while loading, an unnamed thumbnail, an upload announced on every render, or a color that is not a theme token. | -| Governing rules | `spec:AST-082` FR3, FR7; `spec:AST-086` FR8; `spec:AST-093` FR1–FR5; the Swift component API, theme environment, and accessibility (X1, X3, X4, X6, X7) records under `packages/native/specs/swift/library/`. | +| Governing rules | `spec:AST-082` FR3, FR7; `spec:AST-086` FR8; `spec:AST-093` FR1–FR5; the Swift component API, theme environment, and accessibility (X1, X3, X4, X6, X7) records under `packages/native/specs/swift/`. | This table is a review projection; the body below is authoritative. @@ -85,7 +85,7 @@ Consumer migration instructions belong in consumer docs and release notes. | FR2 | `isLoading` with no `src` MUST show a skeleton in `color.skeleton` at 25% opacity; after 1 second it climbs to 92.5% in ten equal held steps over core's default 550 ms medium duration and back, repeating, as core's does, and it rests at 25% under Reduce Motion; under Increase Contrast its color moves 30% toward `color.textPrimary`, as core's does under `prefers-contrast: more`. `isLoading` with a `src` MUST draw `color.overlay` over the image with the platform's small progress view in `color.onDark`, which keeps its size at any text size. | core `Thumbnail`; Swift accessibility X6 | verify | | FR3 | With `onRemove`, a 20-point remove button MUST sit `space.s1` from the top trailing corner on `color.overlay` with the shared `close` `Icon` at `xsm` in `color.onDark`, which the theme can remap and which keeps its size at any text size. It is visible at rest with `always` and on touch devices; with `hover` under a pointer, it appears while the pointer is over an enabled thumbnail or while its open or remove button has keyboard focus; a pointer reveals nothing on a disabled thumbnail, as core turns pointer events off there. | core `Thumbnail` `showRemoveOn`; `spec:AST-082` FR3 | verify | | FR4 | With `onClick`, not disabled, and not loading, the tile MUST be a SwiftUI `Button` that paints `color.overlayPressed` while pressed. `isDisabled` MUST draw the thumbnail at half opacity and turn off its tap and remove button. The open button while loading or disabled, and the remove button while disabled, MUST stay mounted but inert, so focus on them stays put and VoiceOver reads them dimmed (`spec:AST-085`; Swift accessibility X2); during an upload the remove button stays active, as core's does. | core `Thumbnail` | verify | -| FR5 | With a `label`, the thumbnail MUST show it through the Astryx tooltip (`architecture:native-swift-library-tooltip`), as core's tooltip wraps the whole thumbnail: not a tab stop itself, keyboard focus on its open or remove button shows the label, and a direct tap opens it while an open action still runs, as the Tooltip by input table below states. A disabled thumbnail shows none, and the label is not a hint, since it is in the thumbnail's name. | core `Thumbnail` `label`; core `Tooltip`; `spec:AST-082` FR3 | verify | +| FR5 | With a `label`, the thumbnail MUST show it through the Astryx tooltip (`architecture:native-swift-tooltip`), as core's tooltip wraps the whole thumbnail: not a tab stop itself, keyboard focus on its open or remove button shows the label, and a direct tap opens it while an open action still runs, as the Tooltip by input table below states. A disabled thumbnail shows none, and the label is not a hint, since it is in the thumbnail's name. | core `Thumbnail` `label`; core `Tooltip`; `spec:AST-082` FR3 | verify | | FR6 | Both initializers MUST build the same `ThumbnailProps`; a single trailing closure is `onClick`. Base props come through `ThumbnailProps`: with `isHidden` the view draws nothing; `id` becomes the accessibility identifier. | Swift component API A2–A4, A7; `spec:AST-082` FR7 | verify | **Tooltip by input (FR5).** The Astryx tooltip's rules apply; this table diff --git a/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.swift b/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.swift index 1d6e9178b61c2..beab00576d91a 100644 --- a/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.swift +++ b/packages/native/apple/Sources/Astryx/Thumbnail/Thumbnail.swift @@ -71,7 +71,7 @@ public struct Thumbnail: View { .frame(width: Thumbnail.side, height: Thumbnail.side) .opacity(isDisabled ? 0.5 : 1) .onHover { isHovering = $0 } - // The Astryx tooltip (library/tooltip.spec.md), as core's Thumbnail wraps + // The Astryx tooltip (specs/swift/tooltip.spec.md), as core's Thumbnail wraps // itself in core's tooltip: not a tab stop, and core's `auto` touch opens // it on a tap, since the wrapper is not a control, while an open action // still runs. The label is already in the name, so no hint. diff --git a/packages/native/apple/Sources/Astryx/Tooltip/TooltipPresenter.swift b/packages/native/apple/Sources/Astryx/Tooltip/TooltipPresenter.swift index c02c3362a0dae..893cb72cc294d 100644 --- a/packages/native/apple/Sources/Astryx/Tooltip/TooltipPresenter.swift +++ b/packages/native/apple/Sources/Astryx/Tooltip/TooltipPresenter.swift @@ -38,7 +38,7 @@ enum TooltipTouch: Equatable, Sendable { extension View { /// The Astryx tooltip, one behavior for every component, following core's - /// `useTooltip` and `Tooltip` (`library/tooltip.spec.md`). + /// `useTooltip` and `Tooltip` (`specs/swift/tooltip.spec.md`). /// - Parameters: /// - text: The tooltip's words; `nil` shows nothing. /// - hint: Words VoiceOver reads as the hint, as core puts given tooltip @@ -57,7 +57,7 @@ extension View { } } -/// Core's tooltip on Apple platforms (`library/tooltip.spec.md`): +/// Core's tooltip on Apple platforms (`specs/swift/tooltip.spec.md`): /// /// - The bubble sits above the view, as core places it, below when there is /// no room above, and slides sideways to stay inside the window's safe area diff --git a/packages/native/apple/Tests/AstryxTests/AvatarTests.swift b/packages/native/apple/Tests/AstryxTests/AvatarTests.swift index 124ff18c39b3b..08d9220812704 100644 --- a/packages/native/apple/Tests/AstryxTests/AvatarTests.swift +++ b/packages/native/apple/Tests/AstryxTests/AvatarTests.swift @@ -206,7 +206,7 @@ private func onWhite(_ color: Color) -> [Int] { } // FR8: Avatar's words, focus, touch, and hint for the shared tooltip -// (library/tooltip.spec.md), whose inputs `TooltipPresenterTests.swift` covers. +// (specs/swift/tooltip.spec.md), whose inputs `TooltipPresenterTests.swift` covers. @Test func aStaticAvatarIsATabStopAndOpensOnATapAndAControlKeepsBoth() { // Core's `auto` touch resolves from focus: a static avatar opens on a tap, diff --git a/packages/native/apple/Tests/AstryxTests/TooltipPresenterTests.swift b/packages/native/apple/Tests/AstryxTests/TooltipPresenterTests.swift index 7c8072291b693..5087f1c36f3ff 100644 --- a/packages/native/apple/Tests/AstryxTests/TooltipPresenterTests.swift +++ b/packages/native/apple/Tests/AstryxTests/TooltipPresenterTests.swift @@ -5,7 +5,7 @@ import SwiftUI import Testing @testable import Astryx -// The Astryx tooltip (library/tooltip.spec.md), one cell at a time. +// The Astryx tooltip (specs/swift/tooltip.spec.md), one cell at a time. @Test func tooltipsWaitCoresDelayAndBridge() { #expect(TooltipPresenter.delay == .milliseconds(200)) @@ -134,7 +134,7 @@ import Testing @Test func noSwiftViewUsesHelpForTooltips() throws { // `.help` is also the VoiceOver hint and never shows on focus or a tap, so - // every tooltip goes through `astryxTooltip` (library/tooltip.spec.md). + // every tooltip goes through `astryxTooltip` (specs/swift/tooltip.spec.md). let sources = URL(fileURLWithPath: #filePath) .deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() .appendingPathComponent("Sources") diff --git a/packages/native/scripts/swift-ios-theme.mjs b/packages/native/scripts/swift-ios-theme.mjs index e09c39a3ae3b1..5ae1bd061fafc 100644 --- a/packages/native/scripts/swift-ios-theme.mjs +++ b/packages/native/scripts/swift-ios-theme.mjs @@ -9,7 +9,7 @@ * surface) and one Theme.swift per theme declaring * Theme. * @position Implements the Swift theme generator spec - * (specs/swift/library/theme-generator.md). Run by `generate:swift-ios`; the + * (specs/swift/theme-generator.spec.md). Run by `generate:swift-ios`; the * Swift output is committed and a test fails when any file drifts. * * SYNC: When modified, update this header and the theme generator spec. diff --git a/packages/native/specs/README.md b/packages/native/specs/README.md index 69c88cc3141cf..4c7c6af6885e6 100644 --- a/packages/native/specs/README.md +++ b/packages/native/specs/README.md @@ -1,25 +1,41 @@ -# Native component specs +# Native specs -One short spec per native component, per language, kept in the component's own -folder beside its implementation, the way core keeps `TabList.spec.md` next to -`TabList.tsx`. The rules live in +Every native spec is a standard knowledge record +([`architecture:knowledge-contracts`](../../../docs/architecture/knowledge-contracts.md) +INV27), checked by `pnpm check:knowledge`. The rules live in [AST-093 (Native component guidelines)](../../../docs/specs/AST-093-native-component-guidelines/spec.md), which applies [AST-082 (Native-first surfaces)](../../../docs/specs/AST-082-native-first-surfaces/spec.md). +- **A native component's spec** sits beside its implementation in the folder + where core keeps the component, the way core keeps `TabList.spec.md` next to + `TabList.tsx`: usually its own folder, + `packages/native/apple/Sources/Astryx//.spec.md`, and core's + folder when core keeps it in another (`Avatar/AvatarStatusDot.spec.md`, + `List/ListItem.spec.md`). It is a + `component` record + with id `component:swift/` and `platform_of: component:`, built + from + [`docs/templates/knowledge/component-spec.md`](../../../docs/templates/knowledge/component-spec.md). + It states only what the platform adds or must do differently; the core + record keeps the shared contract. +- **Rules shared by a language's components** are architecture records in + `specs//.spec.md`, with id + `architecture:native--`, built from + [`docs/templates/knowledge/architecture.md`](../../../docs/templates/knowledge/architecture.md). + ```text packages/native/ ├── specs/ -│ ├── TEMPLATE.md # copy this for a new component spec │ └── swift/ # rules shared by every Swift component -│ └── library/ # rules every Swift component or build step shares +│ └── .spec.md # architecture:native-swift- └── apple/Sources/Astryx/ └── / # one folder per component, named as core's ├── .swift # declares in the Astryx module - └── .spec.md # the component's spec, e.g. Button.spec.md + └── .spec.md # component:swift/ ``` Swift comes first. Kotlin and other languages get their own directory when -their first component starts. +their first shared rule starts. ## The three guidelines, in order @@ -39,9 +55,10 @@ the same everywhere. A native spec never renames or changes it. ## Adding a spec -1. Copy `TEMPLATE.md` to the component's folder as `.spec.md`, for - Swift `apple/Sources/Astryx//.spec.md`. -2. Fill every section. Write "None" rather than deleting one. +1. Copy the template for the record's kind from `docs/templates/knowledge/` to + the location above, and fill its frontmatter and every section. +2. Run `pnpm check:knowledge` and the rubric in + `docs/contributing/spec-writing.md`. 3. Open a pull request that changes only files under `packages/native/`. A spec can land on its own or with the component it describes. diff --git a/packages/native/specs/TEMPLATE.md b/packages/native/specs/TEMPLATE.md deleted file mode 100644 index 53da3081cc173..0000000000000 --- a/packages/native/specs/TEMPLATE.md +++ /dev/null @@ -1,61 +0,0 @@ -# () - - - -## Summary - - - -## Contract - -- Core component: `` (core contract in `packages/native/src/components/.ts`). -- Platforms: ``. -- Platform props: - -## Native control - - - -## Platform guidelines - - - -## Brand through the theme - -| What | Theme value | -| -------------- | ------------------------------------- | -| | | - - - -## States - - - -## Accessibility - - - -## Test and validation - -- Unit tests: -- Contract: parameters match the generated contract; drift tests pass. -- Accessibility: -- Theme: renders from two different themes with no value of its own. -- Captures: every state, light and dark, largest text size, from a simulator or - device. - -## Open questions - - diff --git a/packages/native/specs/swift/README.md b/packages/native/specs/swift/README.md index 483cf0c063c03..270694acaef71 100644 --- a/packages/native/specs/swift/README.md +++ b/packages/native/specs/swift/README.md @@ -1,11 +1,26 @@ # Swift specs -Rules shared by every Astryx component written in Swift, for iOS and macOS. +Rules shared by every Astryx component written in Swift, for iOS and macOS, as +architecture records (`architecture:native-swift-`), one concern per +file. A component spec follows these and records only where its component +differs. Each is a knowledge record, governed like any other; all are current +except `tooltip.spec.md`, which is a draft. -A single component's spec lives in that component's own folder, beside its -views: `packages/native/apple/Sources/Astryx//.spec.md`, named as the -core component (`Button.spec.md`, `TextInput.spec.md`) and copied from -[`../TEMPLATE.md`](../TEMPLATE.md). +| File | Concern | +| ------------------------------ | ------------------------------------------------------------------------------------- | +| `accessibility.spec.md` | How core's accessibility semantics reach VoiceOver and Apple's accessibility settings | +| `augmentable-variants.spec.md` | How a theme's own variants reach Swift as open types | +| `component-api.spec.md` | How a SwiftUI view is named and takes its generated contract | +| `slots.spec.md` | How each slot kind (text, icon, content) appears in SwiftUI | +| `styles.spec.md` | How Astryx's look ships as SwiftUI styles | +| `tooltip.spec.md` | How core's tooltips reach pointer, keyboard, touch, and VoiceOver (internal) | +| `theme-environment.spec.md` | How the theme rides on SwiftUI's environment in both directions | +| `theme-generator.spec.md` | How any Astryx theme becomes a Swift `Theme` | -Start with a component that already has a generated contract in -`packages/native/src/components/`. +A single component's spec lives beside its views in the folder where core +keeps the component: usually its own, +`packages/native/apple/Sources/Astryx//.spec.md`, named as the +core component (`Button.spec.md`, `TextInput.spec.md`), and core's folder when +core keeps it in another (`Avatar/AvatarStatusDot.spec.md`). See +[`../README.md`](../README.md). Start with a component that already has a +generated contract in `packages/native/src/components/`. diff --git a/packages/native/specs/swift/library/accessibility.md b/packages/native/specs/swift/accessibility.spec.md similarity index 73% rename from packages/native/specs/swift/library/accessibility.md rename to packages/native/specs/swift/accessibility.spec.md index f7ca7cc339345..73c1bd525f355 100644 --- a/packages/native/specs/swift/library/accessibility.md +++ b/packages/native/specs/swift/accessibility.spec.md @@ -1,11 +1,31 @@ -# Accessibility contract (Swift) +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-accessibility +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-23 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: [.github/scripts/native-swift.sh] +deciding_specs: + [ + spec:AST-093/FR3, + spec:AST-093/FR5, + spec:AST-085, + spec:AST-086, + spec:AST-082/FR17, + ] +--- -- Status: draft -- Relation: extends AST-093 FR3 and FR5. Applies the core accessibility - records AST-085 (focusable disabled controls) and AST-086 (status - announcements) to VoiceOver and Apple's accessibility settings. +# Accessibility architecture (Swift) -## Summary + + +## Purpose A VoiceOver user hears the same name, role, state, and messages from an Astryx component on iPhone as a screen-reader user hears on the web: a message that @@ -13,7 +33,13 @@ appears is announced once, and one already on screen when it opens is not. Peopl who turn on Reduce Motion, Reduce Transparency, Bold Text, Button Shapes, or Differentiate Without Color get components that honor each setting. -## Requirements +## System model + +This record extends AST-093 FR3 and FR5. Applies the core accessibility records +AST-085 (focusable disabled controls) and AST-086 (status announcements) to +VoiceOver and Apple's accessibility settings. + +## Boundaries and invariants - **X1 — Name, role, and state come from the core contract.** The contract's accessible name becomes `accessibilityLabel`, its role becomes the matching @@ -54,7 +80,31 @@ Differentiate Without Color get components that honor each setting. - **X7 — Targets are at least 44 by 44 points,** even when the drawn control is smaller. -## Acceptance checks +## Change coupling + +A change to a Swift component's accessibility modifiers, its animations, or +its colors and shapes is reviewed against X1–X7. A change to the core +accessibility contract of a component, or to `spec:AST-085` or +`spec:AST-086`, is reviewed for its effect here. The checks in Verification +prove the record stays current. + +## Owning code + +- `packages/native/` — Swift components and their test targets, which carry + X1–X7 and the checks that prove them. + +## Deciding specs + +- `spec:AST-093/FR3` — Apple platforms follow the Human Interface Guidelines, + including VoiceOver and the accessibility settings. +- `spec:AST-093/FR5` — every native component change ships with accessibility + evidence. +- `spec:AST-085` — disabled controls stay focusable and say why. +- `spec:AST-086` — status messages announce once, at the priority their + severity sets. +- `spec:AST-082/FR17` — icons share names and are decorative unless labeled. + +## Verification | Requirement | Check | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -67,9 +117,16 @@ Differentiate Without Color get components that honor each setting. ## Current state -- On `main`, the Swift package `packages/native/apple` holds generated props, - the wire, and the logic host, and no SwiftUI views. -- No Swift component has accessibility tests or an audit. +- The `Astryx` product in `packages/native/apple` holds the SwiftUI views + Avatar, AvatarStatusDot, Banner, Button, Card, Heading, Icon, Text, + TextInput, and Thumbnail. +- Unit tests assert the accessible names and traits that Avatar, + AvatarStatusDot, Banner, Button, Heading, Icon, TextInput, and Thumbnail set, + and Button's Loading value; Card and Text have no accessibility assertions. + X2 is tested in part (Thumbnail's disabled buttons stay mounted and inert), + and so is X6 (Reduce Motion and Bold Text, and AvatarStatusDot's status + shown by shape as well as color). X3 has no tests, + and no Swift component has an accessibility audit yet. ## Open questions diff --git a/packages/native/specs/swift/library/augmentable-variants.md b/packages/native/specs/swift/augmentable-variants.spec.md similarity index 82% rename from packages/native/specs/swift/library/augmentable-variants.md rename to packages/native/specs/swift/augmentable-variants.spec.md index c8cadeb8d7bbf..0326218f18e83 100644 --- a/packages/native/specs/swift/library/augmentable-variants.md +++ b/packages/native/specs/swift/augmentable-variants.spec.md @@ -1,14 +1,25 @@ -# Augmentable variants (Swift) +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-augmentable-variants +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-24 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: [.github/scripts/native-swift.sh] +deciding_specs: + [spec:AST-082/FR4, spec:AST-082/FR10, spec:AST-082/FR18, spec:AST-093/FR4] +--- -- Status: draft -- Relation: applies the native form of the AST-082 FR4 rule for augmentable - props (`docs/specs/AST-082-native-first-surfaces/spec.md`, FR4 as amended) (a string-backed value with built-in constants, extended by the theme - generator, that decodes any name and draws an unknown name in the base look) - to Swift, and extends `packages/native/specs/swift/library/theme-generator.md`. - Native style data comes from `defineNativeTheme` platform entries (AST-082 - FR10, FR18; AST-093 FR4). +# Augmentable variants architecture (Swift) -## Summary + + +## Purpose A brand that adds its own button or badge variant gets the same variant name on iOS: an iOS developer writes `Button("Save", variant: .primaryMuted)`, it @@ -16,7 +27,16 @@ compiles because the theme declares it, and the person using the app sees it styled as the brand's native theme defines it. A variant name nothing styles draws the component's base look, with no value style, instead of failing. -## Requirements +## System model + +This record applies the native form of the AST-082 FR4 rule for augmentable +props (`docs/specs/AST-082-native-first-surfaces/spec.md`, FR4 as amended) (a +string-backed value with built-in constants, extended by the theme generator, +that decodes any name and draws an unknown name in the base look) to Swift, and +extends `architecture:native-swift-theme-generator`. Native style data comes +from `defineNativeTheme` platform entries (AST-082 FR10, FR18; AST-093 FR4). + +## Boundaries and invariants - **V1 — An augmentable prop is an open, string-backed type.** For every prop the platform's contract keeps that the core contract marks augmentable, the contract generator emits a @@ -68,7 +88,25 @@ ExpressibleByStringLiteral` holding the web value as its raw string, instead only what V3 carries: paint, shape, font, and padding, never behavior. Name, role, and state come from the core contract, as for built-ins. -## Acceptance checks +## Change coupling + +A change to the Swift code or build step this record governs is reviewed against +its requirements, and a change to a record it builds on is reviewed for its +effect here. The checks under Verification prove the record stays current. + +## Owning code + +- `packages/native/scripts/` — the theme generator, which extends each augmentable type with a theme's own variants. +- `packages/native/apple/Sources/Astryx/` — the string-backed variant types and the views that draw them. + +## Deciding specs + +- `spec:AST-082/FR4` +- `spec:AST-082/FR10` +- `spec:AST-082/FR18` +- `spec:AST-093/FR4` + +## Verification | Requirement | Check | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/packages/native/specs/swift/library/component-api.md b/packages/native/specs/swift/component-api.spec.md similarity index 91% rename from packages/native/specs/swift/library/component-api.md rename to packages/native/specs/swift/component-api.spec.md index 6b98283f76681..1fe94fbd01095 100644 --- a/packages/native/specs/swift/library/component-api.md +++ b/packages/native/specs/swift/component-api.spec.md @@ -1,10 +1,37 @@ -# Component API (Swift) +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-component-api +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-26 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: + [ + .github/scripts/native-swift.sh, + packages/native/src/contract/contracts.test.ts, + ] +deciding_specs: + [ + spec:AST-093/FR1, + spec:AST-093/FR5, + spec:AST-082/FR3, + spec:AST-082/FR4, + spec:AST-082/FR5, + spec:AST-082/FR7, + spec:AST-082/FR8, + ] +--- -- Status: draft -- Relation: extends AST-093 FR1 and FR5, and AST-082 FR3, FR4, FR5, FR7, FR8. - It decides how one SwiftUI view exposes its component's generated contract. +# Component API architecture (Swift) -## Summary + + +## Purpose An iOS developer writes `import Astryx`, then `Button("Save", variant: .primary) { save() }`, or @@ -14,7 +41,12 @@ contract does not allow. The person using the app gets the same words, states, and behavior the web gives them, because every Swift view is driven by the same generated contract. -## Requirements +## System model + +This record extends AST-093 FR1 and FR5, and AST-082 FR3, FR4, FR5, FR7, FR8. +It decides how one SwiftUI view exposes its component's generated contract. + +## Boundaries and invariants - **A1 — Core names, in a module named `Astryx`.** The library product and the Swift module that holds the views are both named `Astryx`. A component's @@ -77,7 +109,7 @@ same generated contract. `Props`, for generated and wire-driven callers, and a SwiftUI-style initializer with labeled parameters. Both take every content slot, the labeled one as a `@ViewBuilder` closure and the props one as the slot's - generated node type, as `packages/native/specs/swift/library/slots.md` says, + generated node type, as `architecture:native-swift-slots` says, and both build the view from the same `Props` values; the view's `body` reads only those values and slots. The two initializers, and the convenience initializers that take a slot component's props (slots S3), are one API in @@ -90,7 +122,7 @@ same generated contract. - A `content` slot (AST-082 FR4, with its amendment that lets a platform accept a wider form for a content slot) is a generic `@ViewBuilder` closure parameter that accepts any view, as - `packages/native/specs/swift/library/slots.md` says, never `AnyView`. A + `architecture:native-swift-slots` says, never `AnyView`. A `text` slot is words, a `String` field of `Props` (A5), and an `icon` slot is an `IconName` field. - Parameters without defaults come before those with defaults, except a @@ -127,7 +159,28 @@ same generated contract. its `HStack` and `VStack` wire nodes (AST-082 FR19), which the logic host draws with SwiftUI's stacks. -## Acceptance checks +## Change coupling + +A change to the Swift code or build step this record governs is reviewed against +its requirements, and a change to a record it builds on is reviewed for its +effect here. The checks under Verification prove the record stays current. + +## Owning code + +- `packages/native/apple/Sources/Astryx/` — each component's SwiftUI view and the generated contract it takes. +- `packages/native/src/contract/` — the contract generator whose Swift output the views take. + +## Deciding specs + +- `spec:AST-093/FR1` +- `spec:AST-093/FR5` +- `spec:AST-082/FR3` +- `spec:AST-082/FR4` +- `spec:AST-082/FR5` +- `spec:AST-082/FR7` +- `spec:AST-082/FR8` + +## Verification | Requirement | Check | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/packages/native/specs/swift/library/README.md b/packages/native/specs/swift/library/README.md deleted file mode 100644 index cbde4012b33a6..0000000000000 --- a/packages/native/specs/swift/library/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Swift library specs - -Rules that every Astryx Swift component or Swift build step shares, one -concern per file. A component spec (`.spec.md` in the component's own -folder, such as `apple/Sources/Astryx/Button/Button.spec.md`, from -`../../TEMPLATE.md`) follows these and records only where a component differs. - -These specs apply AST-082 (native-first surfaces) and AST-093 (native component -guidelines). Where one of them and an approved record disagree, the record -wins. - -| File | Concern | -| ------------------------- | ------------------------------------------------------------------------------------- | -| `accessibility.md` | How core's accessibility semantics reach VoiceOver and Apple's accessibility settings | -| `augmentable-variants.md` | How a theme's own variants reach Swift as open types | -| `component-api.md` | How a SwiftUI view is named and takes its generated contract | -| `slots.md` | How each slot kind (text, icon, content) appears in SwiftUI | -| `styles.md` | How Astryx's look ships as SwiftUI styles | -| `tooltip.spec.md` | How core's tooltips reach pointer, keyboard, touch, and VoiceOver (internal) | -| `theme-environment.md` | How the theme rides on SwiftUI's environment in both directions | -| `theme-generator.md` | How any Astryx theme becomes a Swift `Theme` | diff --git a/packages/native/specs/swift/library/slots.md b/packages/native/specs/swift/slots.spec.md similarity index 78% rename from packages/native/specs/swift/library/slots.md rename to packages/native/specs/swift/slots.spec.md index 1a88b16b89030..a388addbb1384 100644 --- a/packages/native/specs/swift/library/slots.md +++ b/packages/native/specs/swift/slots.spec.md @@ -1,13 +1,28 @@ -# Typed slots (Swift) +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-slots +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-24 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: + [ + .github/scripts/native-swift.sh, + packages/native/src/contract/contracts.test.ts, + ] +deciding_specs: [spec:AST-082/FR4, spec:AST-082/FR17] +--- -- Status: draft -- Relation: applies AST-082 FR4 - (`docs/specs/AST-082-native-first-surfaces/spec.md`: typed slots, text, - icon, and content, and a platform's wider form for a content slot with the - same meaning), and FR17 (shared icon names) to Swift. It decides how each slot kind appears in - SwiftUI; it does not define the kinds. +# Typed slots architecture (Swift) -## Summary + + +## Purpose An iOS developer can put an icon in a button, an avatar at the start of a list row, or any SwiftUI view at its end, the way SwiftUI's own `Label` takes an @@ -16,7 +31,15 @@ row lines up and reads the same way. Icons are always ones the platform can draw. The person using the app sees the same parts in the same places as on the web, read by VoiceOver in the same order. -## Requirements +## System model + +This record applies AST-082 FR4 +(`docs/specs/AST-082-native-first-surfaces/spec.md`: typed slots, text, icon, +and content, and a platform's wider form for a content slot with the same +meaning), and FR17 (shared icon names) to Swift. It decides how each slot kind +appears in SwiftUI; it does not define the kinds. + +## Boundaries and invariants - **S1 — Each AST-082 slot kind has one Swift form.** - A **text** slot is a `String`, the contract's exact type (AST-093 FR5). A @@ -71,7 +94,23 @@ places as on the web, read by VoiceOver in the same order. slot holds, for every free-form prop it keeps; the contract generator fails when a kept free-form prop has none. -## Acceptance checks +## Change coupling + +A change to the Swift code or build step this record governs is reviewed against +its requirements, and a change to a record it builds on is reviewed for its +effect here. The checks under Verification prove the record stays current. + +## Owning code + +- `packages/native/apple/Sources/Astryx/` — the views that take each slot kind. +- `packages/native/src/contract/` — the generator that gives each slot its Swift form. + +## Deciding specs + +- `spec:AST-082/FR4` +- `spec:AST-082/FR17` + +## Verification | Requirement | Check | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -83,10 +122,11 @@ places as on the web, read by VoiceOver in the same order. ## Current state -- The contract generator knows two slot kinds, `text` and `content`; an icon - prop has no portable form, so the icon props of Button and Badge are - web-only. -- The built-in icon names map to SF Symbols in TypeScript only. +- The contract generator has a typed icon slot, the generated `IconName`, + which Icon's `icon` prop uses; the generated Button and Badge contracts + still leave out their icon props. +- Swift's Icon draws the built-in icon names as SF Symbols, and a theme can + replace any of them. ## Open questions diff --git a/packages/native/specs/swift/library/styles.md b/packages/native/specs/swift/styles.spec.md similarity index 81% rename from packages/native/specs/swift/library/styles.md rename to packages/native/specs/swift/styles.spec.md index 2f17838dd6d9d..c1d29735785cb 100644 --- a/packages/native/specs/swift/library/styles.md +++ b/packages/native/specs/swift/styles.spec.md @@ -1,12 +1,24 @@ -# SwiftUI styles (Swift) - -- Status: draft -- Relation: extends `packages/native/specs/swift/library/component-api.md`, - reads the theme as `packages/native/specs/swift/library/theme-environment.md` - says, and applies AST-093 FR2 (native-first: a component sits on the platform's - own control). It decides how Astryx's look reaches SwiftUI's own controls. - -## Summary +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-styles +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-24 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: [.github/scripts/native-swift.sh] +deciding_specs: [spec:AST-093/FR2] +--- + +# SwiftUI styles architecture (Swift) + + + +## Purpose An app that already uses SwiftUI's buttons, toggles, and progress views can adopt Astryx's look one control at a time, with @@ -15,7 +27,14 @@ screens. The person using the app keeps the system controls' accessibility and behavior, now in the brand. Astryx's own views are built on the same styles, so a theme change reaches both. -## Requirements +## System model + +This record extends `architecture:native-swift-component-api`, reads the theme +as `architecture:native-swift-theme-environment` says, and applies AST-093 FR2 +(native-first: a component sits on the platform's own control). It decides how +Astryx's look reaches SwiftUI's own controls. + +## Boundaries and invariants - **Y1 — Astryx looks as SwiftUI styles.** For each component whose core contract matches a SwiftUI control whose style protocol accepts a custom @@ -61,7 +80,21 @@ so a theme change reaches both. styled with `.astryx` gets the look and not the view's behavior contract (for example, loading announced once). -## Acceptance checks +## Change coupling + +A change to the Swift code or build step this record governs is reviewed against +its requirements, and a change to a record it builds on is reviewed for its +effect here. The checks under Verification prove the record stays current. + +## Owning code + +- `packages/native/apple/Sources/Astryx/` — the SwiftUI styles and the views built on them. + +## Deciding specs + +- `spec:AST-093/FR2` + +## Verification | Requirement | Check | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -73,8 +106,11 @@ so a theme change reaches both. ## Current state -- On `main`, the Swift package `packages/native/apple` holds generated props, - the wire, and the logic host. There are no SwiftUI views or styles. +- The `Astryx` product in `packages/native/apple` holds the SwiftUI views + Avatar, AvatarStatusDot, Banner, Button, Card, Heading, Icon, Text, + TextInput, and Thumbnail. Button draws + through an internal button style; no public Astryx style for SwiftUI's own + controls exists yet. ## Open questions diff --git a/packages/native/specs/swift/library/theme-environment.md b/packages/native/specs/swift/theme-environment.spec.md similarity index 87% rename from packages/native/specs/swift/library/theme-environment.md rename to packages/native/specs/swift/theme-environment.spec.md index 8129394bce949..f3422f475f146 100644 --- a/packages/native/specs/swift/library/theme-environment.md +++ b/packages/native/specs/swift/theme-environment.spec.md @@ -1,12 +1,24 @@ -# Theme environment (Swift) - -- Status: draft -- Relation: extends AST-093 FR4 and AST-082 FR10, FR11; builds on - `packages/native/specs/swift/library/theme-generator.md` (the `Theme` and - `Tokens` it generates, and its font family reader) and - `packages/native/specs/swift/library/component-api.md` (names). - -## Summary +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-theme-environment +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-25 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: [.github/scripts/native-swift.sh] +deciding_specs: [spec:AST-093/FR4, spec:AST-082/FR10, spec:AST-082/FR11] +--- + +# Theme environment architecture (Swift) + + + +## Purpose In an app built with Astryx, the system's own controls and Astryx components match, and the person's system settings are honored everywhere: dark mode, @@ -16,7 +28,14 @@ theme, stock SwiftUI controls take the theme's accent as their tint wherever it stays readable, and otherwise keep their own colors. Astryx adds no second settings system for an app to keep in step. -## Requirements +## System model + +This record extends AST-093 FR4 and AST-082 FR10, FR11; builds on +`architecture:native-swift-theme-generator` (the `Theme` and `Tokens` it +generates, and its font family reader) and +`architecture:native-swift-component-api` (names). + +## Boundaries and invariants - **E1 — Settings are read from SwiftUI, never duplicated.** Components and the theme read these environment values and Astryx offers no API of its own for @@ -70,7 +89,23 @@ settings system for an app to keep in step. environment value declared with `@Entry`; components read it with `@Environment` and never through a singleton. -## Acceptance checks +## Change coupling + +A change to the Swift code or build step this record governs is reviewed against +its requirements, and a change to a record it builds on is reviewed for its +effect here. The checks under Verification prove the record stays current. + +## Owning code + +- `packages/native/apple/Sources/Astryx/` — the theme environment value and the modifiers that set and read it. + +## Deciding specs + +- `spec:AST-093/FR4` +- `spec:AST-082/FR10` +- `spec:AST-082/FR11` + +## Verification | Requirement | Check | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/packages/native/specs/swift/library/theme-generator.md b/packages/native/specs/swift/theme-generator.spec.md similarity index 94% rename from packages/native/specs/swift/library/theme-generator.md rename to packages/native/specs/swift/theme-generator.spec.md index 0ec7191f602af..ea1c51f00b631 100644 --- a/packages/native/specs/swift/library/theme-generator.md +++ b/packages/native/specs/swift/theme-generator.spec.md @@ -1,10 +1,32 @@ -# Theme generator (Swift) +--- +schema_version: 1 +template_version: 3 +kind: architecture +id: architecture:native-swift-theme-generator +authority: current +archive_reason: null +superseded_by: null +approved_by: cixzhang +approved_at: 2026-10-25 +owners: [cixzhang] +applies_to: [packages/native/] +verified_by: + [.github/scripts/native-swift.sh, scripts/check-package-boundaries.js] +deciding_specs: + [ + spec:AST-082/FR10, + spec:AST-082/FR18, + spec:AST-093/FR4, + spec:AST-082/FR16, + spec:AST-006, + ] +--- -- Status: draft -- Relation: extends AST-082 FR10, FR18 and AST-093 FR4, and applies AST-082 - FR16 and AST-006. It is the one path by which a theme reaches Swift. +# Theme generator architecture (Swift) -## Summary + + +## Purpose An iOS app built with Astryx shows its own brand, not only core's default: any Astryx theme, from a published theme package such as neutral or from the @@ -14,7 +36,12 @@ token is a typed Swift member, so a misspelled token fails to compile instead of drawing nothing. The generator runs core's own resolver, so Swift never re-implements theme logic. -## Requirements +## System model + +This record extends AST-082 FR10, FR18 and AST-093 FR4, and applies AST-082 FR16 +and AST-006. It is the one path by which a theme reaches Swift. + +## Boundaries and invariants - **G1 — Input is a portable theme; core resolves it.** The generator is one function, `renderSwiftTheme(theme, options)`. Its input is a `PortableTheme` @@ -161,7 +188,26 @@ re-implements theme logic. files are committed beside the Swift sources until CI publishes generated Swift to a Swift-only branch or tag, as the package README notes. -## Acceptance checks +## Change coupling + +A change to the Swift code or build step this record governs is reviewed against +its requirements, and a change to a record it builds on is reviewed for its +effect here. The checks under Verification prove the record stays current. + +## Owning code + +- `packages/native/scripts/` — the theme generator and its drift check. +- `packages/native/apple/Sources/Astryx/` — the generated Swift themes. + +## Deciding specs + +- `spec:AST-082/FR10` +- `spec:AST-082/FR18` +- `spec:AST-093/FR4` +- `spec:AST-082/FR16` +- `spec:AST-006` + +## Verification | Requirement | Check | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/packages/native/specs/swift/library/tooltip.spec.md b/packages/native/specs/swift/tooltip.spec.md similarity index 99% rename from packages/native/specs/swift/library/tooltip.spec.md rename to packages/native/specs/swift/tooltip.spec.md index 49faeea4f56e4..04da299670ecc 100644 --- a/packages/native/specs/swift/library/tooltip.spec.md +++ b/packages/native/specs/swift/tooltip.spec.md @@ -2,7 +2,7 @@ schema_version: 1 template_version: 3 kind: architecture -id: architecture:native-swift-library-tooltip +id: architecture:native-swift-tooltip authority: draft archive_reason: null superseded_by: null diff --git a/scripts/check-knowledge.mjs b/scripts/check-knowledge.mjs index 659348f57ebfa..53cb5280d01dc 100644 --- a/scripts/check-knowledge.mjs +++ b/scripts/check-knowledge.mjs @@ -31,8 +31,12 @@ const { } = require('../.github/scripts/component-design-decisions.cjs'); const { classifyComponentKnowledgePath, + classifyPackageArchitecturePath, + classifyPlatformComponentKnowledgePath, isComponentSpecRecordPath, isIgnoredComponentKnowledgeSegment, + isKnowledgeRecordCandidatePath, + isPackageArchitectureRecordPath, } = require('../.github/scripts/knowledge-paths.cjs'); const HERE = path.dirname(fileURLToPath(import.meta.url)); @@ -460,9 +464,9 @@ export function validatePathApplicability( return problems; } -function repositoryFileList(root) { +function repositoryFileList(root, options = []) { try { - return execFileSync('git', ['-C', root, 'ls-files', '-z'], { + return execFileSync('git', ['-C', root, 'ls-files', '-z', ...options], { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'], @@ -485,7 +489,10 @@ function isKnowledgeRecordPath(filePath) { filePath, ); if (themeMatch) return themeMatch[1] === themeMatch[2]; - return isComponentSpecRecordPath(filePath); + return ( + isComponentSpecRecordPath(filePath) || + isPackageArchitectureRecordPath(filePath) + ); } export function collectGlobalReviewMatches( @@ -703,6 +710,42 @@ export function discoverThemeRecordCandidates(root = DEFAULT_ROOT) { return {records: records.sort(), problems}; } +/** + * Every spec-shaped file in the repository (INV27): Markdown named + * `*.spec.md` or under a `specs` directory, outside the paths discovery + * ignores. Each must be a valid record at its kind's canonical location. + */ +function knowledgeRecordCandidates(root) { + // Files git tracks or would track (untracked files outside .gitignore), so a + // new record is checked before it is staged; a directory that is not a + // checkout (a test fixture) is walked instead. + const files = + repositoryFileList(root, ['--cached', '--others', '--exclude-standard']) ?? + matchingFilesRecursively(root, () => true, { + skipDirectory: isIgnoredComponentKnowledgeSegment, + }).map(candidate => toPosixPath(path.relative(root, candidate))); + return files.filter( + filePath => + isKnowledgeRecordCandidatePath(filePath) && + fs.existsSync(path.join(root, filePath)), + ); +} + +function toPosixPath(filePath) { + return filePath.split(path.sep).join('/'); +} + +/** Spec-shaped files that sit at no record kind's canonical location. */ +export function discoverRecordCandidateProblems(root = DEFAULT_ROOT) { + return knowledgeRecordCandidates(root) + .filter(filePath => !isKnowledgeRecordPath(filePath)) + .sort() + .map( + filePath => + `${filePath}: a spec-shaped file must be a knowledge record at its kind's canonical location; packages keep no spec format, template, or location of their own (architecture:knowledge-contracts INV27).`, + ); +} + export function discoverKnowledgeRecords(root = DEFAULT_ROOT) { const records = []; @@ -732,6 +775,16 @@ export function discoverKnowledgeRecords(root = DEFAULT_ROOT) { records.push(...discoverThemeRecordCandidates(root).records); + // Platform component records and package records can sit in any package. + for (const filePath of knowledgeRecordCandidates(root)) { + if ( + classifyPlatformComponentKnowledgePath(filePath) || + classifyPackageArchitecturePath(filePath) + ) { + records.push(path.join(root, filePath)); + } + } + for (const packageName of COMPONENT_PACKAGE_NAMES) { const sourceRoot = path.join(root, `packages/${packageName}/src`); records.push( @@ -751,6 +804,19 @@ export function discoverKnowledgeRecords(root = DEFAULT_ROOT) { function componentRecordLocation(root, absolutePath) { const filePath = path.relative(root, absolutePath).split(path.sep).join('/'); + const platform = classifyPlatformComponentKnowledgePath(filePath); + if (platform) { + const componentRootPath = path.join( + root, + platform.componentTree, + platform.componentRoot, + ); + return { + ...platform, + componentRootPath, + componentSourcePath: componentRootPath, + }; + } const classified = classifyComponentKnowledgePath(filePath); if (!classified) return null; return { @@ -839,6 +905,99 @@ function componentRootDefinesPublicComponent(componentRootPath, publicName) { return false; } +/** + * A platform component record (`packages/native/apple/Sources/Astryx// + * .spec.md`) carries the id `component:/` + * for its path, and `platform_of` names the core component, + * `component:`. The core component's record keeps the shared + * contract; until it has one, the core component's public source and + * documentation stand in. The record sits in the folder where core keeps that + * component (AST-093 FR1): its own folder when core uses one, or the other + * component's folder core keeps it in, as core keeps AvatarStatusDot in + * `Avatar/` and ListItem in `List/`. + */ +function validatePlatformComponentRecord(root, record, location, recordsById) { + const problems = []; + const {frontmatter} = record.document; + const expectedId = `component:${location.language}/${location.publicName}`; + if (frontmatter.get('id') !== expectedId) { + problems.push( + `${record.filePath}: platform component record id must be ${expectedId} to match its path.`, + ); + } + const expectedCore = `component:${location.publicName}`; + const platformOf = frontmatter.get('platform_of'); + if (platformOf !== expectedCore) { + problems.push( + `${record.filePath}: platform_of must be ${expectedCore}, the core component this platform implements.`, + ); + return problems; + } + const coreFolders = new Set(); + for (const target of recordsById.get(platformOf) ?? []) { + const coreLocation = classifyComponentKnowledgePath(target.filePath); + if ( + target.document.frontmatter.get('kind') === 'component' && + coreLocation?.kind === 'component' + ) { + coreFolders.add(coreLocation.componentRoot); + } + } + for (const folder of coreComponentSourceFolders(root, location.publicName)) { + coreFolders.add(folder); + } + if (coreFolders.size === 0) { + problems.push( + `${record.filePath}: platform_of ${platformOf} names no core component: there is no Core or Lab record for it, no packages/core/src/${location.publicName}/, and no ${location.publicName}.doc.mjs under packages/core/src/.`, + ); + return problems; + } + // A record in another component's folder would route that folder's files to + // the wrong record. + if (!coreFolders.has(location.componentRoot)) { + const places = [...coreFolders] + .sort() + .map(folder => `${location.componentTree}/${folder}/`) + .join(' or '); + problems.push( + `${record.filePath}: a platform component record lives in the folder where core keeps its component: ${location.publicName}.spec.md belongs in ${places}.`, + ); + } + return problems; +} + +const coreDocFolders = new Map(); + +/** + * The folders under `packages/core/src/` that hold `publicName`'s public + * source, which stand in for its record: its own component directory + * `/`, and the folder of any consumer doc `.doc.mjs` (a member + * such as ListItem documented inside `List/`). + */ +function coreComponentSourceFolders(root, publicName) { + const coreSrc = path.join(root, 'packages/core/src'); + if (!coreDocFolders.has(coreSrc)) { + const byName = new Map(); + for (const file of matchingFilesRecursively( + coreSrc, + name => name.endsWith('.doc.mjs'), + {skipDirectory: isIgnoredComponentKnowledgeSegment}, + )) { + const name = path.basename(file, '.doc.mjs'); + const folder = path.relative(coreSrc, file).split(path.sep)[0]; + if (!byName.has(name)) byName.set(name, new Set()); + byName.get(name).add(folder); + } + coreDocFolders.set(coreSrc, byName); + } + const folders = new Set(coreDocFolders.get(coreSrc).get(publicName) ?? []); + const directory = path.join(coreSrc, publicName); + if (fs.existsSync(directory) && fs.statSync(directory).isDirectory()) { + folders.add(publicName); + } + return folders; +} + function activeRecord(record, activeAuthorities) { return activeAuthorities.includes( record.document.frontmatter.get('authority'), @@ -880,7 +1039,25 @@ export function validateComponentModuleRelationships( } const publicName = location.publicName; + if (kind === 'component' && location.layout === 'platform') { + problems.push( + ...validatePlatformComponentRecord(root, record, location, recordsById), + ); + continue; + } + if (kind === 'module' && location.layout === 'platform') { + problems.push( + `${record.filePath}: platform component trees hold component records only; the core record owns its modules.`, + ); + continue; + } if (kind === 'component') { + const platformOf = frontmatter.get('platform_of'); + if (platformOf != null) { + problems.push( + `${record.filePath}: platform_of must be null on a Core or Lab component record; only a platform implementation's record names its core record.`, + ); + } if (location.kind !== 'component') { problems.push( `${record.filePath}: component records must be direct children of their component root; nested records use kind: module.`, @@ -1769,7 +1946,10 @@ export async function validateKnowledgeRoot(root = DEFAULT_ROOT) { const engineeringApprovalOwners = fs.existsSync(engineeringOwnersPath) ? parseOwnerFile(fs.readFileSync(engineeringOwnersPath, 'utf8')) : []; - const problems = [...discoverThemeRecordCandidates(root).problems]; + const problems = [ + ...discoverThemeRecordCandidates(root).problems, + ...discoverRecordCandidateProblems(root), + ]; const ids = new Map(); const records = []; const delegations = []; @@ -1880,9 +2060,24 @@ export async function validateKnowledgeRoot(root = DEFAULT_ROOT) { const latestKindVersion = latestKindVersions.get(kind); if (isActiveRecord && recordVersion !== latestKindVersion) { problems.push( - `${filePath}: active ${kind} records must use latest schema_version ${latestKindVersion} for that kind.`, + `${filePath}: active ${kind} records must use latest schema_version ${latestKindVersion} for that kind.${kind === 'component' && latestKindVersion === 5 ? ' Run node scripts/migrate-knowledge-v5.mjs.' : ''}`, ); } + const packageRecord = classifyPackageArchitecturePath(filePath); + if (packageRecord) { + if (kind !== packageRecord.kind) { + problems.push( + `${filePath}: a package record under specs/ must be kind: ${packageRecord.kind}.`, + ); + } + if (document.frontmatter.get('id') !== packageRecord.id) { + problems.push( + `${filePath}: package record id must be ${packageRecord.id} to match its path.`, + ); + } + } + const isPlatformRecord = + classifyPlatformComponentKnowledgePath(filePath) !== null; if (kind === 'component' || kind === 'module') { const designDecisions = parseDesignDecisionsBlock(content, filePath); problems.push( @@ -1890,7 +2085,11 @@ export async function validateKnowledgeRoot(root = DEFAULT_ROOT) { ); const parsed = parseAnatomyThemingBlock(content, filePath); problems.push(...parsed.problems); - if (parsed.mapping != null) { + if (parsed.mapping != null && isPlatformRecord) { + problems.push( + `${filePath}: a platform component record carries no anatomy-theming map; its core record owns the anatomy.`, + ); + } else if (parsed.mapping != null) { if (isActiveRecord) { delegations.push(...collectDelegations(parsed.mapping, filePath)); } diff --git a/scripts/check-knowledge.test.mjs b/scripts/check-knowledge.test.mjs index c04cc1d0474cf..bf3ce63f7c965 100644 --- a/scripts/check-knowledge.test.mjs +++ b/scripts/check-knowledge.test.mjs @@ -39,7 +39,9 @@ function fixtureRoot() { path.join(root, 'docs/templates/knowledge'), {recursive: true}, ); - for (const version of ['v1.json', 'v2.json', 'v3.json', 'v4.json']) { + for (const version of fs + .readdirSync(path.join(repoRoot, 'docs/schemas/knowledge')) + .filter(name => /^v[0-9]+\.json$/.test(name))) { fs.copyFileSync( path.join(repoRoot, `docs/schemas/knowledge/${version}`), path.join(root, `docs/schemas/knowledge/${version}`), @@ -87,10 +89,11 @@ function addSchemaVersion(root, version) { function componentRecord(overrides = {}) { const values = { - schema_version: '3', + schema_version: '5', template_version: '1', kind: 'component', id: 'component:Button', + platform_of: 'null', authority: 'draft', archive_reason: 'null', superseded_by: 'null', @@ -2137,10 +2140,10 @@ describe('knowledge validation', () => { fs.mkdirSync(directory); fs.writeFileSync( path.join(directory, 'Button.spec.md'), - componentRecord({template_version: '8'}), + componentRecord({template_version: '9'}), ); expect((await validateKnowledgeRoot(root)).join('\n')).toMatch( - /template_version 8 is newer than 7/, + /template_version 9 is newer than 8/, ); }); @@ -2154,7 +2157,7 @@ describe('knowledge validation', () => { componentRecord({schema_version: '0'}), ); expect((await validateKnowledgeRoot(root)).join('\n')).toMatch( - /active component records must use latest schema_version 3 for that kind/, + /active component records must use latest schema_version 5 for that kind/, ); }); @@ -2365,6 +2368,306 @@ describe('INV25 record reachability', () => { expect(await validateKnowledgeRoot(root)).toEqual([]); }); + it('fails a spec-shaped file at no record location (INV27)', async () => { + const root = fixtureRoot(); + for (const relative of [ + 'packages/native/specs/TEMPLATE.md', + 'packages/native/specs/swift/library/accessibility.md', + 'packages/widgets/docs/Widget.spec.md', + 'apps/demo/specs/notes.md', + ]) { + fs.mkdirSync(path.dirname(path.join(root, relative)), {recursive: true}); + fs.writeFileSync(path.join(root, relative), '# Not a record\n'); + } + for (const relative of [ + 'packages/native/specs/README.md', + 'packages/native/specs/swift/plan.md', + 'packages/native/specs/__fixtures__/fixture.md', + 'packages/widgets/node_modules/dep/Dep.spec.md', + 'packages/widgets/src/.Hidden.spec.md', + ]) { + fs.mkdirSync(path.dirname(path.join(root, relative)), {recursive: true}); + fs.writeFileSync(path.join(root, relative), '# Not a candidate\n'); + } + const problems = await validateKnowledgeRoot(root); + expect(problems.map(problem => problem.split(':')[0]).sort()).toEqual([ + 'apps/demo/specs/notes.md', + 'packages/native/specs/TEMPLATE.md', + 'packages/native/specs/swift/library/accessibility.md', + 'packages/widgets/docs/Widget.spec.md', + ]); + expect(problems[0]).toContain('INV27'); + }); + + it('accepts a package record whose kind and id follow its path', async () => { + const root = fixtureRoot(); + const template = fs.readFileSync( + path.join(root, 'docs/templates/knowledge/architecture.md'), + 'utf8', + ); + const recordPath = path.join( + root, + 'packages/native/specs/swift/accessibility.spec.md', + ); + fs.mkdirSync(path.dirname(recordPath), {recursive: true}); + const record = template.replace( + 'id: architecture:', + 'id: architecture:native-swift-accessibility', + ); + fs.writeFileSync(recordPath, record); + expect(await validateKnowledgeRoot(root)).toEqual([]); + expect( + discoverKnowledgeRecords(root).map(filePath => + path.relative(root, filePath).split(path.sep).join('/'), + ), + ).toContain('packages/native/specs/swift/accessibility.spec.md'); + + fs.writeFileSync( + recordPath, + record.replace( + 'id: architecture:native-swift-accessibility', + 'id: architecture:swift-accessibility', + ), + ); + expect(await validateKnowledgeRoot(root)).toEqual([ + 'packages/native/specs/swift/accessibility.spec.md: package record id must be architecture:native-swift-accessibility to match its path.', + ]); + }); + + describe('platform component records (DEC-13)', () => { + const platformPath = + 'packages/native/apple/Sources/Astryx/Button/Button.spec.md'; + + function writeCore(root) { + const directory = path.join(root, 'packages/core/src/Button'); + fs.mkdirSync(directory, {recursive: true}); + fs.writeFileSync( + path.join(directory, 'Button.spec.md'), + componentRecord(), + ); + } + + function writePlatform(root, overrides = {}) { + fs.mkdirSync(path.dirname(path.join(root, platformPath)), { + recursive: true, + }); + fs.writeFileSync( + path.join(root, platformPath), + componentRecord({ + id: 'component:swift/Button', + platform_of: 'component:Button', + ...overrides, + }), + ); + } + + it('accepts a platform record that names its core record', async () => { + const root = fixtureRoot(); + writeCore(root); + writePlatform(root); + expect(await validateKnowledgeRoot(root)).toEqual([]); + }); + + it('rejects a platform record whose id does not follow its path', async () => { + const root = fixtureRoot(); + writeCore(root); + writePlatform(root, {id: 'component:Button'}); + expect(await validateKnowledgeRoot(root)).toContain( + `${platformPath}: platform component record id must be component:swift/Button to match its path.`, + ); + }); + + it('accepts core source in place of a core record, until one exists', async () => { + const root = fixtureRoot(); + fs.mkdirSync(path.join(root, 'packages/core/src/Button'), { + recursive: true, + }); + writePlatform(root); + expect(await validateKnowledgeRoot(root)).toEqual([]); + }); + + it('rejects a platform record saved in another component folder', async () => { + const root = fixtureRoot(); + const recordPath = + 'packages/native/apple/Sources/Astryx/Icon/Banner.spec.md'; + fs.mkdirSync(path.dirname(path.join(root, recordPath)), { + recursive: true, + }); + fs.mkdirSync(path.join(root, 'packages/core/src/Banner'), { + recursive: true, + }); + fs.writeFileSync( + path.join(root, recordPath), + componentRecord({ + id: 'component:swift/Banner', + platform_of: 'component:Banner', + }), + ); + expect(await validateKnowledgeRoot(root)).toContain( + `${recordPath}: a platform component record lives in the folder where core keeps its component: Banner.spec.md belongs in packages/native/apple/Sources/Astryx/Banner/.`, + ); + }); + + it('accepts a member beside its family, where core keeps its record (Avatar/AvatarStatusDot)', async () => { + const root = fixtureRoot(); + const avatarDir = path.join(root, 'packages/core/src/Avatar'); + fs.mkdirSync(avatarDir, {recursive: true}); + fs.writeFileSync( + path.join(avatarDir, 'AvatarStatusDot.doc.mjs'), + "export const docs = {name: 'AvatarStatusDot'};\n", + ); + fs.writeFileSync( + path.join(avatarDir, 'AvatarStatusDot.spec.md'), + componentRecord({id: 'component:AvatarStatusDot'}), + ); + const recordPath = + 'packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.spec.md'; + fs.mkdirSync(path.dirname(path.join(root, recordPath)), { + recursive: true, + }); + fs.writeFileSync( + path.join(root, recordPath), + componentRecord({ + id: 'component:swift/AvatarStatusDot', + platform_of: 'component:AvatarStatusDot', + }), + ); + expect(await validateKnowledgeRoot(root)).toEqual([]); + }); + + it('rejects a member in a folder that is neither its own nor its family', async () => { + const root = fixtureRoot(); + const listDir = path.join(root, 'packages/core/src/List'); + fs.mkdirSync(listDir, {recursive: true}); + fs.writeFileSync( + path.join(listDir, 'ListItem.doc.mjs'), + "export const docs = {name: 'ListItem'};\n", + ); + const recordPath = + 'packages/native/apple/Sources/Astryx/Card/ListItem.spec.md'; + fs.mkdirSync(path.dirname(path.join(root, recordPath)), { + recursive: true, + }); + fs.writeFileSync( + path.join(root, recordPath), + componentRecord({ + id: 'component:swift/ListItem', + platform_of: 'component:ListItem', + }), + ); + expect(await validateKnowledgeRoot(root)).toContain( + `${recordPath}: a platform component record lives in the folder where core keeps its component: ListItem.spec.md belongs in packages/native/apple/Sources/Astryx/List/.`, + ); + }); + + it('rejects a member in its own folder when core keeps it beside its family', async () => { + const root = fixtureRoot(); + const listDir = path.join(root, 'packages/core/src/List'); + fs.mkdirSync(listDir, {recursive: true}); + fs.writeFileSync( + path.join(listDir, 'ListItem.doc.mjs'), + "export const docs = {name: 'ListItem'};\n", + ); + const recordPath = + 'packages/native/apple/Sources/Astryx/ListItem/ListItem.spec.md'; + fs.mkdirSync(path.dirname(path.join(root, recordPath)), { + recursive: true, + }); + fs.writeFileSync( + path.join(root, recordPath), + componentRecord({ + id: 'component:swift/ListItem', + platform_of: 'component:ListItem', + }), + ); + expect(await validateKnowledgeRoot(root)).toContain( + `${recordPath}: a platform component record lives in the folder where core keeps its component: ListItem.spec.md belongs in packages/native/apple/Sources/Astryx/List/.`, + ); + }); + + it('accepts a member beside its family, where core documents it (List/ListItem)', async () => { + const root = fixtureRoot(); + const listDir = path.join(root, 'packages/core/src/List'); + fs.mkdirSync(listDir, {recursive: true}); + fs.writeFileSync( + path.join(listDir, 'ListItem.doc.mjs'), + "export const docs = {name: 'ListItem'};\n", + ); + const recordPath = + 'packages/native/apple/Sources/Astryx/List/ListItem.spec.md'; + fs.mkdirSync(path.dirname(path.join(root, recordPath)), { + recursive: true, + }); + fs.writeFileSync( + path.join(root, recordPath), + componentRecord({ + id: 'component:swift/ListItem', + platform_of: 'component:ListItem', + }), + ); + expect(await validateKnowledgeRoot(root)).toEqual([]); + }); + + it('rejects a platform record that names no core component', async () => { + const root = fixtureRoot(); + writePlatform(root); + expect(await validateKnowledgeRoot(root)).toEqual([ + `${platformPath}: platform_of component:Button names no core component: there is no Core or Lab record for it, no packages/core/src/Button/, and no Button.doc.mjs under packages/core/src/.`, + ]); + writePlatform(root, {platform_of: 'null'}); + expect(await validateKnowledgeRoot(root)).toEqual([ + `${platformPath}: platform_of must be component:Button, the core component this platform implements.`, + ]); + }); + + it('fails a component spec outside the Swift component tree', async () => { + const root = fixtureRoot(); + writeCore(root); + const stray = + 'packages/native/apple/Sources/AstryxNative/Button/Button.spec.md'; + fs.mkdirSync(path.dirname(path.join(root, stray)), {recursive: true}); + fs.writeFileSync( + path.join(root, stray), + componentRecord({ + id: 'component:swift/Button', + platform_of: 'component:Button', + }), + ); + expect((await validateKnowledgeRoot(root)).join('\n')).toContain( + `${stray}: a spec-shaped file must be a knowledge record at its kind's canonical location`, + ); + }); + + it('rejects platform_of on a Core record', async () => { + const root = fixtureRoot(); + const directory = path.join(root, 'packages/core/src/Button'); + fs.mkdirSync(directory, {recursive: true}); + fs.writeFileSync( + path.join(directory, 'Button.spec.md'), + componentRecord({platform_of: 'component:Other'}), + ); + expect(await validateKnowledgeRoot(root)).toEqual([ + "packages/core/src/Button/Button.spec.md: platform_of must be null on a Core or Lab component record; only a platform implementation's record names its core record.", + ]); + }); + + it('rejects an anatomy-theming map in a platform record', async () => { + const root = fixtureRoot(); + writeCore(root); + writePlatform(root); + fs.writeFileSync( + path.join(root, platformPath), + withAnatomyTheming( + fs.readFileSync(path.join(root, platformPath), 'utf8'), + {Root: {target: 'button'}}, + ), + ); + expect(await validateKnowledgeRoot(root)).toContain( + `${platformPath}: a platform component record carries no anatomy-theming map; its core record owns the anatomy.`, + ); + }); + }); + it('skips reachability when the root has no tracked file list', async () => { const root = fixtureRoot(); writeSystemSpec(root, 'AST-900', systemSpecRecord()); diff --git a/scripts/migrate-knowledge-v5.mjs b/scripts/migrate-knowledge-v5.mjs new file mode 100644 index 0000000000000..c89a28712d6b0 --- /dev/null +++ b/scripts/migrate-knowledge-v5.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +// Copyright (c) Meta Platforms, Inc. and affiliates. + +/** + * @file Moves component records to knowledge schema v5. + * @input Every tracked `*.spec.md` whose frontmatter has `kind: component`. + * @output Records rewritten in place: `schema_version: 5`, and + * `platform_of: null` after `id` when the record has no `platform_of`. + * @position Schema v5 adds `platform_of` to component records. The rewrite is + * deterministic and idempotent, so a branch can drop its migration commit, + * rebase, and rerun this to migrate records that landed in the meantime. + * + * Usage: node scripts/migrate-knowledge-v5.mjs [--check] + * --check exit 1, listing the records, if any still needs migrating. + */ + +/* global console, process */ +import fs from 'node:fs'; +import path from 'node:path'; +import {execFileSync} from 'node:child_process'; +import {fileURLToPath} from 'node:url'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +/** + * The v5 form of one record's text, or null when the record is not a + * component record or is already on schema 5. + */ +export function migrateComponentRecord(content) { + const match = /^---\n([\s\S]*?)\n---\n/.exec(content); + if (!match) return null; + const frontmatter = match[1]; + if (!/^kind: component$/m.test(frontmatter)) return null; + const version = /^schema_version: (\d+)$/m.exec(frontmatter); + if (!version || Number(version[1]) >= 5) return null; + let next = frontmatter.replace(/^schema_version: \d+$/m, 'schema_version: 5'); + if (!/^platform_of:/m.test(next)) { + next = next.replace(/^(id: .*)$/m, '$1\nplatform_of: null'); + } + return `---\n${next}\n---\n${content.slice(match[0].length)}`; +} + +/** + * Every `*.spec.md` git knows or would add: tracked, and untracked but not + * ignored, as `check:knowledge` discovers records, so a new record it flags is + * migrated before it is staged. + */ +export function specRecordFiles(root) { + return execFileSync( + 'git', + [ + '-C', + root, + 'ls-files', + '-z', + '--cached', + '--others', + '--exclude-standard', + '--', + '*.spec.md', + ], + {encoding: 'utf8'}, + ) + .split('\0') + .filter(Boolean); +} + +function main() { + const check = process.argv.includes('--check'); + const pending = []; + for (const filePath of new Set(specRecordFiles(ROOT))) { + const absolute = path.join(ROOT, filePath); + // A tracked record moved or deleted but not yet staged is not there to + // migrate; check:knowledge skips it the same way. + if (!fs.existsSync(absolute)) continue; + const migrated = migrateComponentRecord(fs.readFileSync(absolute, 'utf8')); + if (migrated == null) continue; + pending.push(filePath); + if (!check) fs.writeFileSync(absolute, migrated); + } + if (check && pending.length > 0) { + console.error( + `${pending.length} component record(s) need schema v5; run node scripts/migrate-knowledge-v5.mjs:\n${pending.map(file => `- ${file}`).join('\n')}`, + ); + process.exit(1); + } + console.log( + check + ? 'Every component record is on schema v5.' + : `Migrated ${pending.length} component record(s) to schema v5.`, + ); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) main(); diff --git a/scripts/migrate-knowledge-v5.test.mjs b/scripts/migrate-knowledge-v5.test.mjs new file mode 100644 index 0000000000000..3b832a6a92f39 --- /dev/null +++ b/scripts/migrate-knowledge-v5.test.mjs @@ -0,0 +1,104 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +/** + * @file Verifies the schema v5 component-record migration. + * @input Record texts in each starting state. + * @output Confirms the rewrite adds platform_of after id, bumps the schema, + * leaves everything else byte-identical, and is idempotent. + * @position Regression coverage for scripts/migrate-knowledge-v5.mjs. + */ + +import {execFileSync} from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {describe, expect, it} from 'vitest'; +import { + migrateComponentRecord, + specRecordFiles, +} from './migrate-knowledge-v5.mjs'; + +const record = (fields, body = '\n# Button component contract\n\nBody.\n') => + `---\n${fields.join('\n')}\n---\n${body}`; + +describe('schema v5 component-record migration', () => { + it('bumps the schema and adds platform_of after id, changing nothing else', () => { + const before = record([ + 'schema_version: 3', + 'template_version: 7', + 'kind: component', + 'id: component:Button', + 'authority: current', + ]); + expect(migrateComponentRecord(before)).toBe( + record([ + 'schema_version: 5', + 'template_version: 7', + 'kind: component', + 'id: component:Button', + 'platform_of: null', + 'authority: current', + ]), + ); + }); + + it('is idempotent and leaves other kinds alone', () => { + const migrated = migrateComponentRecord( + record(['schema_version: 3', 'kind: component', 'id: component:Button']), + ); + expect(migrateComponentRecord(migrated)).toBeNull(); + expect( + migrateComponentRecord( + record(['schema_version: 3', 'kind: module', 'id: module:Button/x']), + ), + ).toBeNull(); + expect(migrateComponentRecord('# No frontmatter\n')).toBeNull(); + }); + + it('keeps an existing platform_of', () => { + expect( + migrateComponentRecord( + record([ + 'schema_version: 3', + 'kind: component', + 'id: component:swift/Button', + 'platform_of: component:Button', + ]), + ), + ).toBe( + record([ + 'schema_version: 5', + 'kind: component', + 'id: component:swift/Button', + 'platform_of: component:Button', + ]), + ); + }); + + it('finds untracked records as check:knowledge does, and skips ignored ones', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'migrate-v5-')); + const git = (...args) => execFileSync('git', ['-C', root, ...args]); + git('init', '-q'); + fs.mkdirSync(path.join(root, 'packages/core/src/Button'), { + recursive: true, + }); + fs.writeFileSync(path.join(root, '.gitignore'), 'dist/\n'); + fs.writeFileSync( + path.join(root, 'packages/core/src/Button/Button.spec.md'), + 'tracked\n', + ); + git('add', '.'); + fs.mkdirSync(path.join(root, 'packages/core/src/Kbd'), {recursive: true}); + fs.writeFileSync( + path.join(root, 'packages/core/src/Kbd/Kbd.spec.md'), + 'new\n', + ); + fs.mkdirSync(path.join(root, 'dist'), {recursive: true}); + fs.writeFileSync(path.join(root, 'dist/Old.spec.md'), 'ignored\n'); + expect(specRecordFiles(root).sort()).toEqual([ + 'packages/core/src/Button/Button.spec.md', + 'packages/core/src/Kbd/Kbd.spec.md', + ]); + fs.rmSync(root, {recursive: true, force: true}); + }); +}); diff --git a/scripts/specs-for-file.mjs b/scripts/specs-for-file.mjs index 34c4536e64ff0..4df410b183435 100644 --- a/scripts/specs-for-file.mjs +++ b/scripts/specs-for-file.mjs @@ -31,6 +31,7 @@ const require = createRequire(import.meta.url); const {componentPackage} = require('./component-packages.cjs'); const { classifyComponentKnowledgePath, + classifyPlatformComponentKnowledgePath, } = require('../.github/scripts/knowledge-paths.cjs'); const HERE = path.dirname(fileURLToPath(import.meta.url)); @@ -48,6 +49,8 @@ const INHERITED_REFERENCE_FIELDS = [ 'system_specs', 'references', 'parent_component', + // A platform component record implements its core record's shared contract. + 'platform_of', ]; /** @@ -144,6 +147,7 @@ export function loadKnowledgeIndex(root = DEFAULT_ROOT) { authority: frontmatter.get('authority'), frontmatter, classified: classifyComponentKnowledgePath(filePath), + platform: classifyPlatformComponentKnowledgePath(filePath), pathPatterns: asList( parsePathApplicabilityBlock(content, filePath).config?.paths, ).filter(pattern => typeof pattern === 'string'), @@ -231,7 +235,8 @@ function fileStem(filePath) { /** * Records whose own canonical location places `filePath` inside their scope: - * the record itself; a theme record over its package; the component record + * the record itself; a theme record over its package; a platform component + * record over its folder, or a member's over its same-stem files there; the component record * named after its root over the whole root; any other component or module * record over its same-stem files anywhere under its component root; a module * record also beside a same-stem `verified_by` file, or over a directory it is @@ -257,6 +262,28 @@ function placementMatches(index, filePath, location) { } continue; } + if (record.platform) { + const {componentTree, componentRoot, publicName} = record.platform; + const platformRoot = `${componentTree}/${componentRoot}/`; + if (!filePath.startsWith(platformRoot)) continue; + // The component named after its folder owns the whole folder; a member + // kept in another component's folder owns only its same-stem files, as + // core routes a member record. + if (publicName === componentRoot) { + matches.push({ + record, + via: 'placement', + detail: `platform component record for ${platformRoot}`, + }); + } else if (stem === publicName) { + matches.push({ + record, + via: 'placement', + detail: `platform component record for ${publicName} in ${platformRoot}`, + }); + } + continue; + } const {classified} = record; if (!classified) continue; const recordDirectory = path.posix.dirname(record.path); diff --git a/scripts/specs-for-file.test.mjs b/scripts/specs-for-file.test.mjs index ff321716e5d68..d8e5e708c4fa6 100644 --- a/scripts/specs-for-file.test.mjs +++ b/scripts/specs-for-file.test.mjs @@ -651,6 +651,91 @@ describe('resolveSpecsForFile', () => { ).toEqual([]); }); + it("routes only a platform member record's own files in the folder it shares", () => { + const root = fixtureRoot(); + for (const [file, id] of [ + [ + 'packages/native/apple/Sources/Astryx/Avatar/Avatar.spec.md', + 'component:swift/Avatar', + ], + [ + 'packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.spec.md', + 'component:swift/AvatarStatusDot', + ], + ]) { + writeRecord(root, file, { + kind: 'component', + id, + platform_of: id.replace('swift/', ''), + }); + } + write(root, 'packages/native/apple/Sources/Astryx/Avatar/Avatar.swift'); + write( + root, + 'packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.swift', + ); + const index = loadKnowledgeIndex(root); + const swiftIds = file => + ids(resolveSpecsForFile(index, file).records).filter(id => + id.includes('swift/'), + ); + expect( + swiftIds('packages/native/apple/Sources/Astryx/Avatar/Avatar.swift'), + ).toEqual(['component:swift/Avatar']); + expect( + swiftIds( + 'packages/native/apple/Sources/Astryx/Avatar/AvatarStatusDot.swift', + ), + ).toEqual(['component:swift/Avatar', 'component:swift/AvatarStatusDot']); + }); + + it('routes a platform component file to its platform record and, through platform_of, its core record', () => { + const root = fixtureRoot(); + writeRecord(root, 'packages/core/src/Button/Button.spec.md', { + kind: 'component', + id: 'component:Button', + platform_of: 'null', + }); + writeRecord( + root, + 'packages/native/apple/Sources/Astryx/Button/Button.spec.md', + { + kind: 'component', + id: 'component:swift/Button', + platform_of: 'component:Button', + }, + ); + write( + root, + 'packages/native/apple/Sources/Astryx/Button/AstryxButton.swift', + ); + write(root, 'packages/native/apple/Sources/Astryx/Card/AstryxCard.swift'); + const index = loadKnowledgeIndex(root); + + const button = resolveSpecsForFile( + index, + 'packages/native/apple/Sources/Astryx/Button/AstryxButton.swift', + ); + expect(ids(button.records)).toEqual([ + 'component:Button', + 'component:swift/Button', + ]); + expect( + button.records.find(record => record.id === 'component:Button').routes, + ).toEqual([ + {via: 'inherited:platform_of', detail: 'component:swift/Button'}, + ]); + + expect( + ids( + resolveSpecsForFile( + index, + 'packages/native/apple/Sources/Astryx/Card/AstryxCard.swift', + ).records, + ), + ).toEqual([]); + }); + it('reports a path no record reaches instead of guessing', () => { const index = loadKnowledgeIndex(popoverFixture()); const result = resolveSpecsForFile(