diff --git a/.vscode/settings.json b/.vscode/settings.json index 2216de0..f7ec3c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "**/packages/*/tsconfig.json": true, "**/packages/*/custom-elements.json": true, "**/packages/*/package-meta.json": true, + "**/packages/*/README.md": true, "**/dist-docs": true, }, @@ -43,6 +44,7 @@ "**/packages/*/tsconfig.json": true, "**/packages/**/custom-elements.json": true, "**/packages/**/package-meta.json": true, + "**/packages/*/README.md": true, "**/dist-docs": true, }, @@ -63,6 +65,7 @@ "**/packages/*/tsconfig.json": true, "**/packages/**/custom-elements.json": true, "**/packages/**/package-meta.json": true, + "**/packages/*/README.md": true, "**/dist-docs": true, }, diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 82dd1a3..0000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,10 +0,0 @@ -# ARCHITECTURE — `@excom/kit-logger` - -One class, no dependencies. `KitLogManager#` → level gate → -`formatArgs([prefix, ...args])` → `summarizeLogArgs` → `console.`. - -The summarizer exists because happy-dom / browser elements `inspect` to -thousands of lines (internals, listener maps, parent chain). Owner rule -(2026-09-11): log output never serializes a DOM node — a label only. - -Boundaries: no DOM writes, no transport, no buffering. diff --git a/DECISIONS.md b/DECISIONS.md deleted file mode 100644 index d44b592..0000000 --- a/DECISIONS.md +++ /dev/null @@ -1,19 +0,0 @@ -# DECISIONS — `@excom/kit-logger` - -## Node summarization is recursive and runs after `formatArgs` — 2026-09-11 - -Decision: `summarizeLogArg` walks plain objects and arrays (depth-limited, -cycle-safe) and replaces every DOM node with `` / `` / -`[Node type=N]`. It is applied to the **output** of `formatArgs`, so a -custom formatter (Quark's table layout) cannot bypass it. Exported for -reuse. - -Context: `QuarkLogger.error({ element, … })` printed a full happy-dom -node tree (~2,700 lines) in `rush retest`; the old summarizer only checked -top-level args and was skipped entirely by custom `formatArgs`. - -Reasoning: The node is never the useful part of a log line; nesting and -formatting are the two ways it escaped, so both are closed at the one -place every line passes through. - -Status: active diff --git a/README.md b/README.md index ebfa99a..091252c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Your HTML __*is*__ the app! Drop-in custom elements that each have a single resp - **Back to the future** Welcome back to building static HTML5 apps. A break from complex JavaScript apps that compile to HTML. - **Little to no JavaScript** You no longer need to write JS for the vast majority of UI cases. You may still call-out to your own pure functions for complex cases. -- **Native++** Just HTML with a derivative of CSS, named Quark, sprinkled on top. +- **Native++** It's real HTML. With a separate, synergistic guest: Quark. - **No magic** No special frameworks, build processes, rendering wizardry, or "HTML-in-my-JS" / "JS-in-my-HTML" DSLs. - **Progressively enhanced** Drop into existing static/server-side-rendered sites. Neutron and Quark can also be used independently. - **Fully composable** Templates, templating, behavior, and custom logic are all decoupled & robust. @@ -61,7 +61,7 @@ Then `import "@excom/nucleus-kit"` and `@import "@excom/nucleus-kit/basic.css"`. Content-rich sites, complex data-driven business rules, progressive enhancement of static/server-rendered pages, embedded user experiences. See [Limitations](https://excom.dev/nucleus/docs/limitations) for the edges. -The Nucleus Stack also opens up novel possibilities that were not easily served by any UI technology before: zero-build-tool UIs (e.g. on-the-fly generation), declarative & auditable target for LLM UI building, plain text assembly to rich UX (like a CMS), incremental upgrading of static/legacy SSR sites, resource-constrained web UIs (especially where scripting needs to be validated or limited, like an ATM), embedded UX (such as upgrading markdown with embedded functionality). +The Nucleus Stack also opens up new possibilities that were not easily served by any UI technology before: zero-build-tool UIs (e.g. on-the-fly generation), declarative & auditable target for LLM UI building, plain text assembly to rich UX (like a CMS), incremental upgrading of static/legacy SSR sites, resource-constrained web UIs (especially where scripting needs to be validated or limited, like an ATM), embedded UX (such as upgrading markdown with embedded functionality). ## Dogfood is nutritious @@ -75,7 +75,11 @@ The Nucleus Stack is MIT licensed and will remain free and open source. This is ## Start here -1. [Quick Start](https://excom.dev/nucleus/docs/quick_start) A working page, in five minutes. -2. [Core Concepts](https://excom.dev/nucleus/docs/core_concepts) The mental model, in one sitting. -3. [Using Elements](https://excom.dev/nucleus/docs/using_elements) and [Orchestrating](https://excom.dev/nucleus/docs/orchestrating) The two skills you'll use daily. -4. [Diving Deeper](https://excom.dev/nucleus/docs/diving_deeper) The architecture behind it all, for the curious and the skeptical. +- [Quick Start - A working page, in five minutes.](https://excom.dev/nucleus/docs/quick_start) +- [Core Concepts - The mental model, in one sitting.](https://excom.dev/nucleus/docs/core_concepts) +- [Using Elements - The Nucleus Kit catalog and how elements behave.](https://excom.dev/nucleus/docs/using_elements) +- [Orchestrating - Get familiar with Quark.](https://excom.dev/nucleus/docs/orchestrating) +- [Styling - Valence.css themes, tokens, and state-driven CSS.](https://excom.dev/nucleus/docs/styling) +- [Building Views - Structure a real app: routes, views, lazy loading.](https://excom.dev/nucleus/docs/building_views) +- Other Guides - [Business Logic](https://excom.dev/nucleus/docs/business_logic), [Creating Elements](https://excom.dev/nucleus/docs/creating_elements), [Best Practices](https://excom.dev/nucleus/docs/best_practices), [Troubleshooting](https://excom.dev/nucleus/docs/troubleshooting), [Debugging with Agents](https://excom.dev/nucleus/docs/debugging_with_agents) +- [Diving Deeper - The architecture behind it all, for the curious and the skeptical.](https://excom.dev/nucleus/docs/diving_deeper) diff --git a/TASKS.md b/TASKS.md deleted file mode 100644 index 648ced3..0000000 --- a/TASKS.md +++ /dev/null @@ -1,4 +0,0 @@ -# TASKS — `@excom/kit-logger` - -No active goal. Known unfixed bug: constructor `level` option precedence -(root TASKS *Test coverage* suspected-bug list). diff --git a/common/changes/@excom/content-carousel/main_2026-09-23-autoplay-test.json b/common/changes/@excom/content-carousel/main_2026-09-23-autoplay-test.json new file mode 100644 index 0000000..6625457 --- /dev/null +++ b/common/changes/@excom/content-carousel/main_2026-09-23-autoplay-test.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@excom/content-carousel", + "comment": "Auto-play tests poll for the advanced slide instead of sleeping a fixed 80 ms (flaked on loaded CI runners); no runtime change", + "type": "patch" + } + ], + "packageName": "@excom/content-carousel", + "email": "133191653+excom-dev@users.noreply.github.com" +} diff --git a/common/changes/@excom/content-carousel/view-dist_2026-09-29-fix-batch.json b/common/changes/@excom/content-carousel/view-dist_2026-09-29-fix-batch.json new file mode 100644 index 0000000..7e055df --- /dev/null +++ b/common/changes/@excom/content-carousel/view-dist_2026-09-29-fix-batch.json @@ -0,0 +1,15 @@ +{ + "changes": [ + { + "packageName": "@excom/content-carousel", + "comment": "Fix an issue where `provision` did not follow slides added or removed, and `last-move` and `content-carousel-slide-changed` did not follow `is-active` moving from one slide to another (a swipe): each change sets one provision, and a carousel moved in the DOM no longer sets it again", + "type": "patch" + }, + { + "packageName": "@excom/content-carousel", + "comment": "Update the internal `autoPlayIntervalId` property to `_autoPlayIntervalId`, as its other private properties are named; it was never an attribute, so markup is unaffected", + "type": "patch" + } + ], + "packageName": "@excom/content-carousel" +} diff --git a/common/changes/@excom/content-drawer/view-dist_2026-09-29-fix-batch.json b/common/changes/@excom/content-drawer/view-dist_2026-09-29-fix-batch.json new file mode 100644 index 0000000..ed44ad9 --- /dev/null +++ b/common/changes/@excom/content-drawer/view-dist_2026-09-29-fix-batch.json @@ -0,0 +1,20 @@ +{ + "changes": [ + { + "packageName": "@excom/content-drawer", + "comment": "Update `content-drawer` so only an `.absolute` drawer styles its parent, as `position: relative; overflow: clip` (was every drawer, with `overflow: hidden`): a fixed or sticky drawer no longer clips its parent, and sticky descendants keep working", + "type": "minor" + }, + { + "packageName": "@excom/content-drawer", + "comment": "Update a closed `content-drawer` to be `visibility: hidden` once its slide-out ends, taking it out of the tab order and the accessibility tree: a custom `transition` on a closed drawer must keep `visibility 0s `, and a closed drawer shown in the layout needs `visibility: visible`", + "type": "minor" + }, + { + "packageName": "@excom/content-drawer", + "comment": "Update the docs: the backdrop never reads the drawer's own variables, so set `--content-drawer-transition-duration`, `--content-drawer-transition-ease` and `--content-drawer-overlay-z-index` on the parent, the backdrop or `:root`", + "type": "none" + } + ], + "packageName": "@excom/content-drawer" +} diff --git a/common/changes/@excom/detect-browser/view-dist_2026-09-28-demos.json b/common/changes/@excom/detect-browser/view-dist_2026-09-28-demos.json new file mode 100644 index 0000000..a2e38ec --- /dev/null +++ b/common/changes/@excom/detect-browser/view-dist_2026-09-28-demos.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/detect-browser", + "comment": "Update the Safari demo to show an install hint instead of loading polyfills", + "type": "none" + } + ], + "packageName": "@excom/detect-browser" +} diff --git a/common/changes/@excom/dom-observer/view-dist_2026-09-28-demos.json b/common/changes/@excom/dom-observer/view-dist_2026-09-28-demos.json new file mode 100644 index 0000000..482724a --- /dev/null +++ b/common/changes/@excom/dom-observer/view-dist_2026-09-28-demos.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/dom-observer", + "comment": "Update the demos to write State from `@on` blocks and `prop(\"provision\")` instead of JS handlers", + "type": "none" + } + ], + "packageName": "@excom/dom-observer" +} diff --git a/common/changes/@excom/event-handler/view-dist_2026-09-28-demos.json b/common/changes/@excom/event-handler/view-dist_2026-09-28-demos.json new file mode 100644 index 0000000..4e92ca2 --- /dev/null +++ b/common/changes/@excom/event-handler/view-dist_2026-09-28-demos.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@excom/event-handler", + "comment": "Update the demos to write State from `@on` blocks and `prop(\"provision\")` instead of JS handlers", + "type": "none" + } + ], + "packageName": "@excom/event-handler" +} diff --git a/common/changes/@excom/fetchable-element/view-dist_2026-09-29-fix-batch.json b/common/changes/@excom/fetchable-element/view-dist_2026-09-29-fix-batch.json new file mode 100644 index 0000000..7033318 --- /dev/null +++ b/common/changes/@excom/fetchable-element/view-dist_2026-09-29-fix-batch.json @@ -0,0 +1,15 @@ +{ + "changes": [ + { + "packageName": "@excom/fetchable-element", + "comment": "Add `did-load` from `loadable-element`: set on the first success, kept while a refresh runs, cleared on an error", + "type": "minor" + }, + { + "packageName": "@excom/fetchable-element", + "comment": "Update failed-request logging to one line: an error status (400 or above) logs a warning, `: request failed` with the response, which the default `KitLogger` level hides; a network or parse failure logs an error; an abort logs nothing", + "type": "minor" + } + ], + "packageName": "@excom/fetchable-element" +} diff --git a/common/changes/@excom/gesture-handler/main_2026-09-24-scroll-handoff.json b/common/changes/@excom/gesture-handler/main_2026-09-24-scroll-handoff.json new file mode 100644 index 0000000..5674d85 --- /dev/null +++ b/common/changes/@excom/gesture-handler/main_2026-09-24-scroll-handoff.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@excom/gesture-handler", + "comment": "Hand a `handoff-ref` overscroll to the gesture only when every scroller between the pointer and the element is at its limit, so content scrolled inside a nested scroller scrolls back before the sheet moves", + "type": "minor" + } + ], + "packageName": "@excom/gesture-handler", + "email": "133191653+excom-dev@users.noreply.github.com" +} diff --git a/common/changes/@excom/heft-rig/main_2026-09-23-dist-files.json b/common/changes/@excom/heft-rig/main_2026-09-23-dist-files.json new file mode 100644 index 0000000..f7eced2 --- /dev/null +++ b/common/changes/@excom/heft-rig/main_2026-09-23-dist-files.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@excom/heft-rig", + "comment": "Package metas read `exports` from the built `dist/exports.generated.json` when package.json has none (it is only applied at publish time), so the docs-site \"View Dist Files\" dialog is no longer empty", + "type": "patch" + } + ], + "packageName": "@excom/heft-rig", + "email": "133191653+excom-dev@users.noreply.github.com" +} diff --git a/common/changes/@excom/include-content/view-dist_2026-09-29-fix-batch.json b/common/changes/@excom/include-content/view-dist_2026-09-29-fix-batch.json new file mode 100644 index 0000000..d2b3c15 --- /dev/null +++ b/common/changes/@excom/include-content/view-dist_2026-09-29-fix-batch.json @@ -0,0 +1,15 @@ +{ + "changes": [ + { + "packageName": "@excom/include-content", + "comment": "Update the docs: a ` @@ -83,7 +89,7 @@ - + @@ -104,9 +110,15 @@ @use "/shell" as *; #release-notice { - @delay 3000 { - is-open: ""; + /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ + &:not([data-did-open]) { + @delay 3000 { + is-open: ""; + data-did-open: ""; + } } + /* prevent clicks from bubbling to the sheet */ + @on mouseup, click (stop-propagation); } provider-fetch[api-url*="package-metas/index.json"][is-success] { @@ -175,16 +187,33 @@ } /* mobile sheet: Escape / back gesture while open; a tapped link closes it */ #site-menu { - &[is-open] dismiss-watcher { is-active: ""; } - &:not([is-open]) dismiss-watcher { is-active: none; } - @on click (target: "spa-a") { is-open: none; } + &[is-open] dismiss-watcher { + is-active: ""; + } + &:not([is-open]) dismiss-watcher { + is-active: none; + } + @on click (target: "spa-a") { + is-open: none; + } } #site-menu-gesture { - &:has(> #site-menu[is-open]) { progress-offset: 1; } - &:not(:has(> #site-menu[is-open])) { progress-offset: 0; } - @on gesture-handler-start { #site-menu { is-scrubbing: ""; } } + &:has(> #site-menu[is-open]) { + progress-offset: 1; + } + &:not(:has(> #site-menu[is-open])) { + progress-offset: 0; + } + @on gesture-handler-start { + #site-menu { + is-scrubbing: ""; + } + } @on gesture-handler-end { - #site-menu { is-open: event.detail.snap == 1; is-scrubbing: none; } + #site-menu { + is-open: event.detail.snap == 1; + is-scrubbing: none; + } } } main { @@ -213,213 +242,221 @@ - + + @@ -427,7 +464,7 @@
Packages
- + @@ -445,11 +482,11 @@
Packages
- + - + Packages data-app="returns-app" data-files="html quark css"> - + @@ -479,7 +516,8 @@
Packages
- + \ No newline at end of file diff --git a/packages/docs-site/package.json b/packages/docs-site/package.json index 3a5bc8e..2263ef3 100644 --- a/packages/docs-site/package.json +++ b/packages/docs-site/package.json @@ -84,4 +84,4 @@ "documented": false, "packageType": "site" } -} +} \ No newline at end of file diff --git a/packages/docs-site/public/apple-touch-icon.png b/packages/docs-site/public/apple-touch-icon.png index 8568e12..0048808 100644 Binary files a/packages/docs-site/public/apple-touch-icon.png and b/packages/docs-site/public/apple-touch-icon.png differ diff --git a/packages/docs-site/public/demo-utils.css b/packages/docs-site/public/demo-utils.css index d77ee4c..9ee0f2f 100644 --- a/packages/docs-site/public/demo-utils.css +++ b/packages/docs-site/public/demo-utils.css @@ -206,8 +206,7 @@ } [id^="demo-provider-fetch-"] > :first-child, -[id^="demo-provider-geolocation-"] > :first-child, -[id^="demo-provider-localstorage-"] > :first-child { +[id^="demo-provider-geolocation-"] > :first-child { output { display: block; white-space: pre-wrap; @@ -217,9 +216,6 @@ } #demo-provider-orientation-request > :first-child { - #orient:not([is-success]):not([is-error]) ~ output { - display: none; - } #orient[is-success] ~ .status, #orient[is-error] ~ .status { display: none; diff --git a/packages/docs-site/public/demo-utils.ts b/packages/docs-site/public/demo-utils.ts index 2d95966..733a57f 100644 --- a/packages/docs-site/public/demo-utils.ts +++ b/packages/docs-site/public/demo-utils.ts @@ -1,94 +1,47 @@ -import { toJsonSafe } from "@excom/kit-utils"; -/** Demo helpers for docs-site live demos (`@use "/demo-utils"`). */ - -/** Write `JSON.stringify(event.detail)` into the nearest / current `output`. */ -export const _setOutputFromDetail = - ({ shouldAppend = false }) => - (e: CustomEvent) => { - const el = e.currentTarget as Element; - const output = ( - el instanceof HTMLOutputElement ? el : el.querySelector("output") - ) as HTMLOutputElement | null; - if (!output) return; - const line = JSON.stringify(toJsonSafe(e.detail ?? {}), null, 2); - if (shouldAppend) { - output.textContent += "\n" + line; - } else { - output.textContent = line; - } - }; - -export const setOutputFromDetail = _setOutputFromDetail({ - shouldAppend: false, -}); -export const appendOutputFromDetail = _setOutputFromDetail({ - shouldAppend: true, -}); - /** - * Write `JSON.stringify(event.target.provision)` into the nearest / current - * `output`. Neutron elements with a `provision` prop dispatch the - * framework-level `neutron-provision` event whenever it's set, so this - * works for any `provider-*` element without a bespoke event name. + * Demo helpers for docs-site live demos (`@use "/demo-utils"`). Each + * function is a `handle:` listener for work Quark has no declaration for. */ -export const setOutputFromElementData = (e: Event) => { - const scope = e.currentTarget as Element; - const output = ( - scope instanceof HTMLOutputElement ? scope : scope.querySelector("output") - ) as HTMLOutputElement | null; - const source = e.target as unknown as { provision?: unknown } | null; - if (!output || !source) return; - output.textContent = JSON.stringify(source.provision ?? null, null, 2); -}; + +/** `localStorage` key the `provider-storage` demo reads. */ +export const DEMO_STORAGE_KEY = "demo-provider-storage"; /** - * Bump a `$count` binding from JS (`quark` js-api demo). Receives the - * owner element from the sheet (`@on click (handle: incrementFromJs(closest(…)))`) - * and returns the click handler; `element.quark.setProperty()` re-runs - * every rule reading `$count` below the owner. + * Stands in for app code writing storage (`provider-storage` demo): stores + * the time, then re-sets `key-name` on every provider of that key. A + * same-tab write fires no `storage` event, so the re-set forces the read. */ -export const incrementFromJs = (owner: Element) => () => { - const current = Number(owner.quark.getPropertyValue("$count") ?? 0); - owner.quark.setProperty("$count", current + 1); -}; - -/** Static list for the `quark` iterate demo. */ -export const getPlanets = () => ["Mercury", "Venus", "Earth", "Mars"]; - -/** Note an intercepted event in the demo's `output` (`quark` events demo). */ -export const noteEvent = (e: Event) => { - const output = (e.currentTarget as Element).parentElement?.querySelector( - "output" +export const seedDemoStorage = () => { + localStorage.setItem( + DEMO_STORAGE_KEY, + JSON.stringify({ seededAt: new Date().toLocaleTimeString() }) ); - if (output) output.textContent = `"${e.type}" handled — navigation prevented`; + document + .querySelectorAll( + `provider-storage[key-name="${DEMO_STORAGE_KEY}"]` + ) + .forEach((provider) => { + provider.keyName = ""; + provider.keyName = DEMO_STORAGE_KEY; + }); }; -/** Noop stand-in for a real Safari polyfill loader (`detect-browser` demo). */ -export const loadPolyfills = (browserInfo) => { - console.log(" polyfill demo data:", browserInfo); - // load polyfills here - return "polyfills loaded"; -}; +/** Feature flags the app already holds (`quark` js-api demo). */ +export const DEMO_FLAGS = { "new-checkout": true, "gift-cards": false }; -/** `localStorage` key seeded by the `provider-storage` demo. */ -export const DEMO_STORAGE_KEY = "demo-provider-storage"; +/** + * Stands in for app JS handing its flags to the document (`quark` js-api + * demo): writes `$app-flags` on the listening element. + */ +export const handFlagsOver = (event: Event) => + (event.currentTarget as Element).quark.setProperty("$app-flags", DEMO_FLAGS); /** - * Writes a fresh payload into `localStorage`, then forces the nearest - * `` to re-read it. The browser's `storage` event only - * fires in *other* tabs, so a same-tab write needs `key-name` re-set. - * It is toggled off and back on around the write. + * Runs a renderable element's render / unrender thunk (`event.detail`) + * inside a view transition (`renderable-element` render-event demo). */ -export const seedDemoStorage = (e: Event) => { - const scope = e.currentTarget as Element; - const el = scope.querySelector("provider-storage") as - | (Element & { keyName: string }) - | null; - if (!el) return; - localStorage.setItem( - DEMO_STORAGE_KEY, - JSON.stringify({ seededAt: new Date().toLocaleTimeString() }) - ); - el.keyName = ""; - el.keyName = DEMO_STORAGE_KEY; +export const renderInTransition = (event: CustomEvent<() => unknown>) => { + if (!document.startViewTransition) return; + event.preventDefault(); + document.startViewTransition(() => event.detail()); }; diff --git a/packages/docs-site/public/views/api-reference/api-reference.css b/packages/docs-site/public/views/api-reference/api-reference.css index 425a18c..57a0c5a 100644 --- a/packages/docs-site/public/views/api-reference/api-reference.css +++ b/packages/docs-site/public/views/api-reference/api-reference.css @@ -40,6 +40,8 @@ data-table { --v-table-sticky-top: var(--site-header-height); --v-spacing: 0.8rem; + /* small screens + unbreakable code tokens (event tables) */ + overflow-x: scroll; code { word-break: keep-all; /* keep hyphenated tokens whole */ overflow-wrap: normal; /* break only at spaces */ diff --git a/packages/docs-site/public/views/install-section/install-section.html b/packages/docs-site/public/views/install-section/install-section.html index 97a079b..a906310 100644 --- a/packages/docs-site/public/views/install-section/install-section.html +++ b/packages/docs-site/public/views/install-section/install-section.html @@ -60,7 +60,7 @@

All exported dist files:

- +