From 0f385f32a88cbaf6f3378f3817d55e944afc0ee8 Mon Sep 17 00:00:00 2001 From: Star Date: Sat, 18 Jul 2026 11:44:16 +0800 Subject: [PATCH] =?UTF-8?q?docs(ui):=20=E5=BB=BA=E7=AB=8B=20AI=20=E6=8C=89?= =?UTF-8?q?=E9=9C=80=E6=96=87=E6=A1=A3=E5=85=A5=E5=8F=A3=EF=BC=88by=20AI?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AI.yaml | 48 ---------------- README.md | 2 +- README.zh-CN.md | 2 +- components/base/API.yaml | 11 +++- components/base/AutoForm.yaml | 16 +++--- components/base/Dialog.yaml | 4 +- components/base/List.yaml | 3 +- components/base/Modal.yaml | 17 ++++-- components/base/Nav.yaml | 35 +++++++----- components/base/Resizer.yaml | 12 ++-- components/base/Toast.yaml | 1 + components/data/Chart.yaml | 42 -------------- components/data/DataGrid.yaml | 32 +++++------ components/form/ColorPicker.yaml | 5 +- components/form/DatePicker.yaml | 5 +- components/form/IconPicker.yaml | 22 +++++--- components/form/TagsInput.yaml | 23 +++++--- frameworks/Bootstrap.yaml | 15 +++-- frameworks/State.yaml | 32 ++++++----- llms.txt | 91 ++++++++++++++++++++++++++++++ package.json | 2 +- tools/build.cjs | 14 +++++ tools/validate-docs.cjs | 96 ++++++++++++++++++++++++++++++++ ui.js | 2 +- ui.min.js | 2 +- utilities/HTTP.yaml | 1 + utilities/VirtualScroll.yaml | 15 +++-- 27 files changed, 356 insertions(+), 194 deletions(-) delete mode 100644 AI.yaml delete mode 100644 components/data/Chart.yaml create mode 100644 llms.txt create mode 100644 tools/validate-docs.cjs diff --git a/AI.yaml b/AI.yaml deleted file mode 100644 index 881fcf2..0000000 --- a/AI.yaml +++ /dev/null @@ -1,48 +0,0 @@ -package: apigo.cc/web/ui - -rules: - - Read only documents required by the task. - - Preserve declarative bindings when extending a component. - - Do not change framework internals unless framework changes are requested. - - Add or update a regression test for every public behavior change. - - Before declaring a public API complete, compare its YAML attributes, properties, methods, events, slots, state fields, options, and rules against named test assertions and examples. Every documented item needs coverage; examples must visibly demonstrate the core behavior declaratively. - - In an ordered value list, the first value is the default. - - Treat each component test as a concise, declarative usage sample: prepare immutable top-level `const` data before DOM parsing, then bind it directly (for example `$.state.list="users"`). Do not add State or window bridge assignments unless shared mutable state is required. - - Prefer framework directives ($if, $bind, $onclick, slots) and Bootstrap classes over imperative page setup or custom CSS. Keep test actions limited to assertions and interaction verification. - - Test pages use full-height flex layouts: the component area fills remaining space and result output is a bounded, internally scrollable region. - - The outer test console keeps the selected test active and represents pass/fail with a visible status badge; detailed output belongs inside the selected test page. - - Public source and YAML use the same PascalCase basename (for example `components/base/List.js` and `components/base/List.yaml`, `frameworks/State.js` and `frameworks/State.yaml`). - -loading: - example: '' - rules: - - Load ui.js in head without async, defer, or type="module". - - Always write components with every public component used by the page. - - Use `base` or `form` to load the documented component category; individual names and category names can be mixed. - - Do not list utilities or framework dependencies. - - ui.js loads frameworks/State.js and frameworks/Bootstrap.js beside itself. - - npm run build writes source modification versions into ui.js; ui.min.js loads matching adjacent .min.js files. - -framework: - - frameworks/State.yaml - - frameworks/Bootstrap.yaml - -components: - - components/base/API.yaml - - components/base/AutoForm.yaml - - components/base/List.yaml - - components/base/Modal.yaml - - components/base/Dialog.yaml - - components/base/Toast.yaml - - components/base/Nav.yaml - - components/base/Resizer.yaml - - components/form/DatePicker.yaml - - components/form/ColorPicker.yaml - - components/form/IconPicker.yaml - - components/form/TagsInput.yaml - - components/data/DataGrid.yaml - - components/data/Chart.yaml - -utilities: - - utilities/HTTP.yaml - - utilities/VirtualScroll.yaml diff --git a/README.md b/README.md index 8a57eca..ca4d900 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,4 @@ Load `ui.js` in `` as a normal script. Do not use `async`, `defer`, or `ty `npm run build` does not bundle source files or create a `dist` directory. It first copies `../state/dist/state.js` and `../bootstrap/dist/bootstrap.js` into `frameworks/`, then writes their source-file modification-time cache versions into `ui.js` and produces adjacent `.min.js` files. Loading `ui.min.js` automatically loads the matching minified framework, utility, and component files. The UI package version remains independent of its framework packages. -For AI-oriented API documentation, start with [AI.yaml](AI.yaml). +For AI-assisted development, give the agent this single entry URL: [https://apigo.cc/web/ui/ai/llms.txt](https://apigo.cc/web/ui/ai/llms.txt). It is a task-oriented index: the agent reads it first, then opens only the linked framework, utility, and component documents required by the current task. The repository copy is [llms.txt](llms.txt). diff --git a/README.zh-CN.md b/README.zh-CN.md index 8a534c4..ee0315e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -12,6 +12,6 @@ 必须把脚本放在 ``,并使用普通 ` + + post_default: | - tests: - API.test.html diff --git a/components/base/AutoForm.yaml b/components/base/AutoForm.yaml index 71b6983..ca70c35 100644 --- a/components/base/AutoForm.yaml +++ b/components/base/AutoForm.yaml @@ -46,8 +46,10 @@ examples: database_fields: | - const fields = [{ id: 'f-role', tableID: 'users', name: 'Role', type: 'v30', settings: { type: 'select', options: ['Admin', 'User'] } }] - const user = { Role: 'Admin' } + submit_api: | - +examples: + basic: | + + rules: - Bind `$bind="Hash.nav"` when the selected navigation item must survive a page refresh. - Use nav for actions such as Logout: `$onnav="if(event.detail.name==='logout') logout()"`. Use change only for selected-page state. - Use the same `navItems` data for horizontal and vertical navigation when comparing layouts. + +tests: + - Nav.test.html diff --git a/components/base/Resizer.yaml b/components/base/Resizer.yaml index 91ea193..e075e67 100644 --- a/components/base/Resizer.yaml +++ b/components/base/Resizer.yaml @@ -1,4 +1,5 @@ name: Resizer +purpose: Resize a sibling or assigned target element by dragging a horizontal or vertical handle. attributes: vertical: Resize height instead of width. @@ -22,12 +23,13 @@ events: rules: - MouseMover is an internal dependency and is loaded automatically. -example: | - - +examples: + horizontal_and_vertical: | + + -
- +
+ tests: - Resizer.test.html diff --git a/components/base/Toast.yaml b/components/base/Toast.yaml index f2f0e36..431b5a5 100644 --- a/components/base/Toast.yaml +++ b/components/base/Toast.yaml @@ -1,4 +1,5 @@ name: Toast +purpose: Show transient notifications and lightweight asynchronous confirmations. methods: show: 'Toast.show(message, options) -> Promise' diff --git a/components/data/Chart.yaml b/components/data/Chart.yaml deleted file mode 100644 index 5100a8c..0000000 --- a/components/data/Chart.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Chart - -constructor: - signature: new Chart(canvas, options) - options: - type: line, bar, or pie. Default is line. - data: Chart.js data object or source object array. - options: Chart.js options object. - map: "{ labels, values, label } for source object arrays." - -component: - attributes: - type: line, bar, or pie. - state: - data: Chart.js data object or source object array. - options: Chart.js options. - map: "{ labels, values, label }" - property: - chartInstance: Underlying Chart instance. - -methods: - update: Update optional data and redraw. - destroy: Destroy the underlying Chart.js instance. - -rules: - - Give the Chart container an explicit height. - - Use map only when data is an array of objects. - - Chart destroys its Chart.js instance when the component unloads. - -example: | - - - -tests: - - apigo.cc/web/chart/test/all.spec.js diff --git a/components/data/DataGrid.yaml b/components/data/DataGrid.yaml index 2db37a1..57595b5 100644 --- a/components/data/DataGrid.yaml +++ b/components/data/DataGrid.yaml @@ -1,4 +1,5 @@ name: DataGrid +purpose: Manage editable, virtualized data-API tables while preserving server field definitions. attributes: editable: @@ -8,8 +9,6 @@ attributes: type: string behavior: POST endpoint for the data-table action protocol. Requires state.table. -purpose: Editable data-API table manager. It consumes the field definitions returned by the data API and keeps them unchanged for save events. - state: table: Table name sent as request.name when api is configured. fields: Array of data-API field definitions; assign before list. @@ -98,20 +97,21 @@ rules: - Treat underscore-prefixed state fields as internal read-only diagnostics. - Custom field editors must be registered in AutoForm before use. -example: | - -
- -
- +examples: + local_and_api: | + +
+ +
+ tests: - DataGrid.test.html diff --git a/components/form/ColorPicker.yaml b/components/form/ColorPicker.yaml index 0ac3d94..f2dfa56 100644 --- a/components/form/ColorPicker.yaml +++ b/components/form/ColorPicker.yaml @@ -25,4 +25,7 @@ rules: - Use $bind for a standalone reactive value; use AutoForm schema type ColorPicker for generated forms. related: - - ../AutoForm.yaml + - ../base/AutoForm.yaml + +tests: + - ColorPicker.test.html diff --git a/components/form/DatePicker.yaml b/components/form/DatePicker.yaml index 60a37e7..f2b5e98 100644 --- a/components/form/DatePicker.yaml +++ b/components/form/DatePicker.yaml @@ -35,4 +35,7 @@ rules: - In AutoForm, declare setting.rangeEnd on the start field; the end field does not need its own schema item. related: - - ../AutoForm.yaml + - ../base/AutoForm.yaml + +tests: + - DatePicker.test.html diff --git a/components/form/IconPicker.yaml b/components/form/IconPicker.yaml index aa76fc3..321649f 100644 --- a/components/form/IconPicker.yaml +++ b/components/form/IconPicker.yaml @@ -4,18 +4,22 @@ purpose: Search and choose a Bootstrap Icons name. properties: value: string; selected icon name without the 'bi-' prefix -example: | - +examples: + standalone_and_form: | + - - + + events: change: detail is the selected icon name related: - - ../AutoForm.yaml + - ../base/AutoForm.yaml + +tests: + - IconPicker.test.html diff --git a/components/form/TagsInput.yaml b/components/form/TagsInput.yaml index 6d74517..392c933 100644 --- a/components/form/TagsInput.yaml +++ b/components/form/TagsInput.yaml @@ -3,21 +3,26 @@ purpose: Enter a list of unique string tags; Enter, comma, or space commits a ta attributes: placeholder: string; input placeholder (the built-in default is localized) + properties: value: array of strings; current unique tags -example: | - +examples: + standalone_and_form: | + - - + + events: change: detail is the complete updated string array related: - - ../AutoForm.yaml + - ../base/AutoForm.yaml + +tests: + - TagsInput.test.html diff --git a/frameworks/Bootstrap.yaml b/frameworks/Bootstrap.yaml index aa6e6d7..b07fa1a 100644 --- a/frameworks/Bootstrap.yaml +++ b/frameworks/Bootstrap.yaml @@ -1,4 +1,5 @@ name: Bootstrap +purpose: Provide Bootstrap 5 styles, icons, JavaScript APIs, and reactive theme configuration. provided: css: 'Bootstrap.js injects Bootstrap 5 CSS and Bootstrap Icons; do not add another Bootstrap stylesheet or runtime.' @@ -15,10 +16,14 @@ rules: - Use Bootstrap classes and Icons directly in component markup. - Do not load a second Bootstrap CSS file or runtime. -examples: | - Bootstrap.config({ primary: '#6f42c1' }) - Bootstrap.config({ bindDarkMode: [LocalStorage, 'darkMode'] }) - bootstrap.Modal.getOrCreateInstance(document.querySelector('#settings')).show() +examples: + theme: | + + native_api: | + tests: - - apigo.cc/web/bootstrap/test/ + - https://apigo.cc/web/bootstrap/raw/branch/main/test/bootstrap.spec.js diff --git a/frameworks/State.yaml b/frameworks/State.yaml index ba07e69..1be9f02 100644 --- a/frameworks/State.yaml +++ b/frameworks/State.yaml @@ -1,4 +1,5 @@ name: State +purpose: Provide reactive state, declarative DOM directives, and the custom component runtime. globals: State: 'Reactive in-memory application state.' @@ -48,19 +49,22 @@ component: - Keep required public behavior declarative; do not expose internal DOM as an application contract. - A bindable component listens for bind and writes event.detail into its state. -examples: | - -
Hello ${users[0].name}
- - - - Component.register('Counter', container => { - container.state.value = 0 - container.addEventListener('bind', event => { container.state.value = event.detail }) - }, Util.makeDom('')) - +examples: + bindings: | + +
Hello ${users[0].name}
+ + + component: | + + tests: - - apigo.cc/web/state/test/ + - https://apigo.cc/web/state/raw/branch/main/test/all.spec.js diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..0c02bff --- /dev/null +++ b/llms.txt @@ -0,0 +1,91 @@ +# @apigo.cc/ui + +> Build-free native JavaScript UI framework. This file is the authoritative AI entry point and documentation index. + +Version: 1.0.1 + +Choose one version-pinned npm CDN. `ui.js` loads all adjacent framework, utility, and component files from the same origin. + +- jsDelivr (primary): `https://cdn.jsdelivr.net/npm/@apigo.cc/ui@1.0.1/ui.js` +- UNPKG (alternative): `https://unpkg.com/@apigo.cc/ui@1.0.1/ui.js` +- JSDMirror (China-optimized alternative): `https://cdn.jsdmirror.com/npm/@apigo.cc/ui@1.0.1/ui.js` + +Load the chosen URL in `` without `async`, `defer`, or `type="module"`. A minimal complete page is: + +```html + + + + + + + + + + + + +``` + +## Retrieval rules + +- Read this file first, then open only the framework, utility, and component documents required by the current task. +- Resolve every relative link from the URL of this file. Linked YAML files are authoritative API contracts. +- Do not preload every linked document. Follow `related` and `tests` references only when they are relevant to the task. +- Preserve declarative bindings when extending a component. +- Do not change framework internals unless framework changes are requested. +- Add or update a regression test for every public behavior change. +- Before declaring a public API complete, compare its YAML attributes, properties, methods, events, slots, state fields, options, and rules against named test assertions and examples. Every documented item needs coverage; examples must visibly demonstrate the core behavior declaratively. +- In an ordered value list, the first value is the default. +- Treat each component test as a concise declarative usage sample: prepare immutable top-level `const` data before DOM parsing, then bind it directly, for example `$.state.list="users"`. Add State or window bridge assignments only when shared mutable state is required. +- Prefer framework directives (`$if`, `$bind`, `$onclick`, slots) and Bootstrap classes over imperative page setup or custom CSS. Keep test actions limited to assertions and interaction verification. +- Test pages use full-height flex layouts: the component area fills remaining space and result output is a bounded, internally scrollable region. +- The outer test console keeps the selected test active and represents pass/fail with a visible status badge; detailed output belongs inside the selected test page. +- Public source and YAML use the same PascalCase basename, for example `components/base/List.js` and `components/base/List.yaml`. + +## Loading rules + +- Always write the `components` attribute with every public component used by the page. +- Use `base`, `form`, or `data` to load a documented component category; individual names and category names can be mixed. +- Do not list utilities or framework dependencies. +- `ui.js` loads `frameworks/State.js` and `frameworks/Bootstrap.js` beside itself. +- `npm run build` writes source modification versions into `ui.js`; `ui.min.js` loads matching adjacent `.min.js` files. + +## Framework + +- [State and declarative directives](frameworks/State.yaml): reactive state, bindings, expressions, components, slots, and lifecycle. +- [Bootstrap integration](frameworks/Bootstrap.yaml): Bootstrap loading and framework integration rules. + +## Base components + +- [API](components/base/API.yaml): declarative HTTP API element. +- [AutoForm](components/base/AutoForm.yaml): schema-driven forms and submission. +- [List](components/base/List.yaml): declarative list rendering and selection. +- [Modal](components/base/Modal.yaml): modal container. +- [Dialog](components/base/Dialog.yaml): message and confirmation dialogs. +- [Toast](components/base/Toast.yaml): transient notifications. +- [Nav](components/base/Nav.yaml): declarative navigation. +- [Resizer](components/base/Resizer.yaml): resizable layout regions. + +## Form components + +- [DatePicker](components/form/DatePicker.yaml): date and date-range input. +- [ColorPicker](components/form/ColorPicker.yaml): color input. +- [IconPicker](components/form/IconPicker.yaml): icon selection. +- [TagsInput](components/form/TagsInput.yaml): editable tag collections. + +## Data components + +- [DataGrid](components/data/DataGrid.yaml): virtualized data table. + +## Utilities + +- [HTTP](utilities/HTTP.yaml): HTTP requests and response handling. +- [VirtualScroll](utilities/VirtualScroll.yaml): virtual scrolling behavior. + +## Optional + +- [README](README.md): package loading and build overview. +- [Changelog](CHANGELOG.md): read only when upgrading or investigating version differences. diff --git a/package.json b/package.json index 6fc8985..c7aff3a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "utilities", "ui.js", "ui.test.html", - "AI.yaml", + "llms.txt", "README.md", "README.zh-CN.md", "test", diff --git a/tools/build.cjs b/tools/build.cjs index 029b209..c67cf71 100644 --- a/tools/build.cjs +++ b/tools/build.cjs @@ -1,6 +1,7 @@ const { copyFile, readdir, readFile, stat, writeFile } = require('node:fs/promises'); const { join, relative, sep } = require('node:path'); const terser = require('terser'); +const { validateDocs } = require('./validate-docs.cjs'); const root = join(__dirname, '..'); const publicDirs = ['frameworks', 'utilities', 'components']; @@ -20,6 +21,19 @@ const filesBelow = async directory => { }; (async () => { + const packageMetadata = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')); + const llmsFile = join(root, 'llms.txt'); + const llmsSource = await readFile(llmsFile, 'utf8'); + const versionLine = /^Version: \S+$/m; + const cdnVersion = /(@apigo\.cc\/ui@)[^/"\s]+(?=\/ui(?:\.min)?\.js)/g; + if (!versionLine.test(llmsSource) || !cdnVersion.test(llmsSource)) throw new Error('llms.txt version markers are missing'); + cdnVersion.lastIndex = 0; + const builtLlms = llmsSource + .replace(versionLine, `Version: ${packageMetadata.version}`) + .replace(cdnVersion, `$1${packageMetadata.version}`); + await writeFile(llmsFile, builtLlms); + await validateDocs(); + await Promise.all(Object.entries(frameworks).map(([name, source]) => copyFile(source, join(root, 'frameworks', `${name}.js`)))); const sources = (await Promise.all(publicDirs.map(dir => filesBelow(join(root, dir))))).flat(); const sourceStats = await Promise.all(sources.map(async file => { diff --git a/tools/validate-docs.cjs b/tools/validate-docs.cjs new file mode 100644 index 0000000..823b841 --- /dev/null +++ b/tools/validate-docs.cjs @@ -0,0 +1,96 @@ +const { access, readFile, readdir } = require('node:fs/promises'); +const { basename, dirname, join, relative, resolve, sep } = require('node:path'); + +const root = join(__dirname, '..'); +const publicDirs = ['frameworks', 'utilities', 'components']; +const posixPath = file => relative(root, file).split(sep).join('/'); + +const filesBelow = async directory => { + const entries = await readdir(directory, { withFileTypes: true }); + const files = await Promise.all(entries.map(entry => { + const file = join(directory, entry.name); + return entry.isDirectory() ? filesBelow(file) : [file]; + })); + return files.flat(); +}; + +const sectionItems = (source, section) => { + const lines = source.split('\n'); + const start = lines.findIndex(line => line === `${section}:`); + if (start < 0) return []; + const items = []; + for (const line of lines.slice(start + 1)) { + const match = line.match(/^ - (.+)$/); + if (!match) break; + items.push(match[1]); + } + return items; +}; + +const validateDocs = async () => { + const errors = []; + const [packageMetadata, llms, uiSource] = await Promise.all([ + readFile(join(root, 'package.json'), 'utf8').then(JSON.parse), + readFile(join(root, 'llms.txt'), 'utf8'), + readFile(join(root, 'ui.js'), 'utf8') + ]); + + const localLinks = [...llms.matchAll(/\]\(([^)]+)\)/g)] + .map(match => match[1]) + .filter(link => !/^[a-z]+:/i.test(link)); + for (const link of localLinks) { + const file = resolve(root, link.split(/[?#]/, 1)[0]); + try { await access(file); } catch (_) { errors.push(`llms.txt links to missing file: ${link}`); } + } + + const documentedComponents = new Map( + localLinks + .filter(link => /^components\/.+\.yaml$/.test(link)) + .map(link => [basename(link, '.yaml'), link]) + ); + const modulesBlock = uiSource.match(/const modules = \{([\s\S]*?)\n \};/)?.[1] || ''; + const implementedComponents = new Map( + [...modulesBlock.matchAll(/^ (\w+): \{ path: '(components\/.+)\.js'/gm)] + .map(match => [match[1], `${match[2]}.yaml`]) + ); + for (const [name, file] of documentedComponents) { + if (implementedComponents.get(name) !== file) errors.push(`llms.txt documents component not registered by ui.js: ${name}`); + } + for (const [name, file] of implementedComponents) { + if (documentedComponents.get(name) !== file) errors.push(`ui.js registers component missing from llms.txt: ${name}`); + } + + const yamlFiles = (await Promise.all(publicDirs.map(dir => filesBelow(join(root, dir))))) + .flat() + .filter(file => file.endsWith('.yaml')); + for (const file of yamlFiles) { + const source = await readFile(file, 'utf8'); + const path = posixPath(file); + const name = source.match(/^name: (.+)$/m)?.[1]; + if (!name) errors.push(`${path} is missing name`); + if (name && basename(file, '.yaml') !== name) errors.push(`${path} name must match its basename`); + if (!/^purpose: .+$/m.test(source)) errors.push(`${path} is missing purpose`); + if (!/^examples:/m.test(source)) errors.push(`${path} is missing examples`); + if (/^(example|property):/m.test(source)) errors.push(`${path} must use plural examples/properties`); + + for (const section of ['related', 'tests']) { + for (const item of sectionItems(source, section)) { + if (/^[a-z]+:\/\//i.test(item)) continue; + const target = resolve(dirname(file), item); + try { await access(target); } catch (_) { errors.push(`${path} ${section} points to missing file: ${item}`); } + } + } + } + + const versions = [...llms.matchAll(/@apigo\.cc\/ui@([^/`"\s]+)/g)].map(match => match[1]); + if (!versions.length || versions.some(version => version !== packageMetadata.version)) { + errors.push(`llms.txt CDN versions must all equal package.json version ${packageMetadata.version}`); + } + + if (errors.length) throw new Error(`AI documentation validation failed:\n- ${errors.join('\n- ')}`); + console.log(`Validated AI documentation (${localLinks.length} links; ${yamlFiles.length} YAML files; ${documentedComponents.size} components)`); +}; + +if (require.main === module) validateDocs().catch(error => { console.error(error); process.exit(1); }); + +module.exports = { validateDocs }; diff --git a/ui.js b/ui.js index 3c37bc7..56a0c55 100644 --- a/ui.js +++ b/ui.js @@ -1,6 +1,6 @@ (() => { // Updated by `npm run build`. Source files stay directly executable. - const build = { version: 'mrjbct5d', files: {"frameworks/Bootstrap.js":"mrjbct5d","frameworks/State.js":"mrjbct5d","utilities/HTTP.js":"mrg6t8zm","utilities/MouseMover.js":"mrg6t8zm","utilities/VirtualScroll.js":"mrg6t8zn","components/base/API.js":"mrj8lfwz","components/base/AutoForm.js":"mrj10yku","components/base/Dialog.js":"mrh7tjfb","components/base/List.js":"mrge46xl","components/base/Modal.js":"mrj1zcg3","components/base/Nav.js":"mrj6haqp","components/base/Resizer.js":"mrghfzrh","components/base/Toast.js":"mrghiycy","components/data/DataGrid.js":"mrj8lxpb","components/form/ColorPicker.js":"mrgn837c","components/form/DatePicker.js":"mrgn837c","components/form/IconPicker.js":"mrgoatz5","components/form/TagsInput.js":"mrgn837b"} }; + const build = { version: 'mrpt6n5q', files: {"frameworks/Bootstrap.js":"mrpt6n5q","frameworks/State.js":"mrpt6n5p","utilities/HTTP.js":"mrg6t8zm","utilities/MouseMover.js":"mrg6t8zm","utilities/VirtualScroll.js":"mrg6t8zn","components/base/API.js":"mrj8lfwz","components/base/AutoForm.js":"mrj10yku","components/base/Dialog.js":"mrh7tjfb","components/base/List.js":"mrge46xl","components/base/Modal.js":"mrj1zcg3","components/base/Nav.js":"mrj6haqp","components/base/Resizer.js":"mrghfzrh","components/base/Toast.js":"mrghiycy","components/data/DataGrid.js":"mrj8lxpb","components/form/ColorPicker.js":"mrgn837c","components/form/DatePicker.js":"mrgn837c","components/form/IconPicker.js":"mrgoatz5","components/form/TagsInput.js":"mrgn837b"} }; const script = document.currentScript; if (!script || document.readyState !== 'loading') { throw new Error('ui.js must be loaded by a normal