Skip to content

Add a live demo page and show the mark in the page header - #10

Merged
cmm-cmm merged 9 commits into
mainfrom
claude/forgedialog-library-mhkpak
Sep 2, 2026
Merged

cmm-cmm merged 9 commits into
mainfrom
claude/forgedialog-library-mhkpak

Conversation

@cmm-cmm

@cmm-cmm cmm-cmm commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

The site advertised the library but barely demonstrated it, and never displayed its own mark.

Demos. The landing page had four hero buttons and the appearance playground; its "examples" section was static code with a Copy button and nothing that ran. The full demo — drawers, sheets, toasts, the palette, forms, wizards — was demo/index.html at the repository root, which loads ../dist/ directly and is never deployed.

site/demo.html runs all of it at /demo, against the same vendored build the rest of the site uses: alert, confirm, prompt, a validated prompt, open(), stacking, animation: 'none' and 'slide', drawers on both sides, bottom sheets, three toast tones, the lightbox, loading states, the command palette, the notification centre, form(), wizard(), and formWizard().

Each card pairs a control that really runs with the snippet of the call being made, so the page cannot drift from what it documents — a wrong snippet would show up as a misbehaving button beside it. A plugin registered on the page logs afterOpen and afterClose, and every demo prints its return value, so the result log is also evidence the hooks fire.

demo.js uses a single delegated listener keyed by data-demo. The site's CSP is script-src 'self', so inline handlers would be blocked; a demo that throws prints the failure into the log rather than only the console.

The mark. site/icon.svg was only ever a favicon and an apple-touch icon — no page displayed it. It now sits in the header of every page beside the wordmark, with alt="" because the adjacent text already names the product and a screen reader should not announce it twice.

No library code changed. Nothing under src/ was touched, so there is no API, runtime, or accessibility impact on the published package.

Where the API calls came from

They were taken from the reviewed report in etc/ and from demo/index.html, not written from memory, and the global surface was read out of a real browser first (Object.keys(window.ForgeDialog)) so nothing here calls an export that does not exist. That check is also what surfaced form, formWizard, lightbox, loading, notificationCenter, getNotificationHistory, and clearNotificationHistory as demo-worthy — the root demo page covers none of them.

Wiring

/demo is in the nav on every page, in sitemap.xml and llms.txt (via the pages list in scripts/build-site.mjs), and carries WebPage + BreadcrumbList JSON-LD. Like /docs it is the extensionless URL, because Workers Assets and the local server both serve that form and redirect the other.

Branch history

PR #9 was squashed onto main as 631f43c, so this branch was restarted from main with the demo work on top. The remote branch still pointed at the pre-squash commits, whose content main already carries. A force push was declined by policy, so those commits are recorded as a merge parent with -s ours instead — the tree is byte-identical before and after that merge, and the diff against main is only the nine files below.

Verification

  • npm run validate
  • npm run test:e2e — not run locally (the pinned Playwright wants a browser build this container does not have). It drives demo/index.html and the library, neither of which changed. CI runs it.
  • Added or updated tests covering the change — the demo page is presentation over APIs that unit and browser tests already cover; it has no behaviour of its own.

Driven in Chromium against wrangler dev, so the routing and the production _headers CSP are the real ones:

  • All 22 demo controls clicked in sequence: every one surfaces what it claims, with zero console errors and zero failed requests
  • The result log records return values and both plugin lifecycle hooks
  • The header mark renders on all four pages (naturalWidth 150, displayed at 26px) — checked as rendered, not merely present in the markup
  • /, /demo, /docs return 200 and an unmatched path returns 404 with the site's own page
  • Version badge reads v0.7.0 from the library's own export on every page
  • Zero horizontal overflow at 320, 380, 480, 768 and 1280px on all four pages

Checklist

  • Added a changeset (npm run changeset) — not applicable: nothing in the published package changes, and site/ is not published.
  • Refreshed API reports — not applicable, the public surface is untouched.
  • Raised a gzip budget — not needed; npm run size and the tarball check pass unchanged (120.3 KiB, 71 files).
  • Refreshed the visual baseline — not needed; demo/index.html, which the screenshot test drives, is unchanged.

Screenshots

Verified in the browser run rather than captured: the demo page's card grid at desktop and 320px, the header with the mark beside the wordmark, and each surface as its button was clicked.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4


Generated by Claude Code

ForgeDialog had a README and a manual test page but nothing to point people
at. site/ is a landing page with an interactive appearance playground,
written in plain HTML, CSS, and browser JavaScript so the site carries no
dependencies of its own, matching the library it advertises.

The page runs the build from this checkout rather than a CDN:
scripts/build-site.mjs copies dist/index.global.js and dist/index.css into
site/vendor/, which is git-ignored. It uses only Node built-ins and rewrites
no source files. The version badge reads the library's own VERSION export at
runtime, so nothing has to be kept in sync by hand.

The playground calls instance.update() on every control change, so the dialog
restyles while it is open, and it prints the matching source alongside, so the
demo doubles as documentation for the appearance API.

site/_headers ships a strict CSP for Cloudflare Pages limited to same-origin
scripts and styles, which the library already satisfies: test:csp enforces
that dist contains no eval or new Function. docs/website.md covers local use
and the Cloudflare build settings.

Verified in Chromium: no console errors or failed requests, the alert traps
focus and returns it to the trigger on Escape, the playground applies a 30px
radius and casts its shadow right at 90 degrees, live updates reach an open
dialog, and the theme toggle drives the library and the page together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
…ise for search and answer engines

The site had a landing page and an emoji favicon; it now has a full API
reference, a real mark, and the metadata that search engines and answer
engines read.

Naming: the product is Forge Dialog, two words, and every page and document
now says so. Identifiers keep the one-word spelling because that is what they
are: the npm package (forgedialog), the browser global (ForgeDialog), the
custom element (forge-dialog), and the repository. Nothing under src/ changed,
so the published API and its runtime strings are untouched.

Docs: site/docs.html is a 23-section reference written against the reviewed
API report in etc/, covering every option, method, and entry point. The shared
chrome (version badge, theme toggle, copy buttons) moved out of site.js into
common.js so both pages use it without the landing page's demo wiring.

Icon: site/icon.svg is the source of truth and the favicon browsers load.
scripts/build-icons.mjs renders the committed apple-touch-icon.png and
og-image.png from it through Chromium, which is already a dev dependency. The
touch variant squares off the mark's ground because iOS applies its own mask.
The first draft of the OG image used a gradient and cost 296 KB; a flat ground
looks the same at preview size and costs 37 KB.

SEO: canonical, og:url, and og:image are absolute. The canonical tag in
index.html is the single source of truth for the domain — build-site.mjs reads
it back to generate sitemap.xml, robots.txt, and llms.txt, so moving the site
is an edit to the canonical tags and nothing else. Both pages carry JSON-LD;
the FAQ entries are generated from the visible questions so the markup and the
page cannot disagree.

Answer engines: llms.txt follows the llmstxt.org convention and states the
facts models most often get wrong, with the version read from package.json so
it cannot drift. robots.txt names the AI crawlers explicitly rather than
leaving them to the default, and deliberately does not disallow /vendor/ —
blocking a site's own CSS and JS is what breaks rendering for crawlers.

Two layout bugs found while measuring, both pre-existing:

- The header never wrapped, so at 380px the page scrolled sideways by 102px.
- The playground's grid track had an automatic minimum of min-content, which
  the long code lines inside .snippet pushed open — 812px of horizontal
  overflow at 320px. Capping the track at minmax(0, 1fr) and giving .snippet
  min-width: 0 fixes it.

Both pages now fit at 320, 380, 480, and 768px with zero horizontal overflow.

Verified in Chromium against the production CSP: no console errors and no
failed requests on either page, the inline JSON-LD survives script-src 'self'
(it is a data block, not an executable script), all 23 sidebar links resolve
to real sections, scroll-spy tracks the section being read, the hero alert
still gets role="alertdialog" with focus returning to its trigger, and
robots.txt, sitemap.xml, llms.txt, and all three icons serve with the right
content types. npm run validate and format:check pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
The Cloudflare Worker was built with `npm run build` and deployed with
`wrangler deploy --assets ./dist`, so it uploaded 97 files of compiled
library — .mjs, .cjs, .d.ts, .css — with no index.html among them. Workers
Assets had nothing to answer `/` with, which is why the domain served
nothing.

wrangler.jsonc now pins the assets directory to `site/`, the directory
`npm run site:build` actually fills, so the configuration lives in the
repository rather than in a dashboard field. A deploy command that passes
`--assets` overrides it, so docs/website.md says to drop the flag.

While verifying the routing, the docs page turned out to be reachable only
by redirect. Workers Assets serves `foo.html` at `/foo` and 307s the
extension form, and `serve` (which `npm run site` uses) does the same, so
every URL naming `/docs.html` — the canonical tag, og:url, the TechArticle
and BreadcrumbList nodes, the sitemap entry, the llms.txt link, and the
in-page links — pointed at a URL that redirects. A canonical or sitemap
entry that redirects is a conflicting signal, so `/docs` is now the URL
everywhere, and the docs page links back to `./` rather than
`./index.html`.

Verified against `wrangler dev` with the production `_headers` CSP: both
pages and every asset return 200, every relative link resolves without a
redirect, the pages load with no console errors or failed requests, the
JSON-LD parses, and the hero alert still opens as `role="alertdialog"` and
closes on Escape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
A repo-wide pass before the worker is recreated from scratch.

An unmatched path got the platform's bare 404, so site/404.html now answers
for them through not_found_handling. It links its assets root-relative
rather than with "./" because it also answers for nested paths, where a
relative href would resolve against the missing directory, and it carries
noindex with no canonical tag since it has no URL of its own.

The sitemap stamped every entry with the build date, which told crawlers
both pages change daily. lastmod now comes from the last commit that
touched each page's HTML. A build with no reachable git history writes the
entry without a lastmod rather than with a plausible-looking date.

npm run check:release reported what the docs had not caught up with:
forgedialog 0.7.0 was published on 1 September. docs/releasing.md still
said the package was unpublished and framed the manual first publish and
the E404 troubleshooting entry as the likely path; both are now scoped to a
fork starting from an unpublished name. package.json homepage moves from
the README anchor to the site, listed under Unreleased because 0.7.0 is
already out and cannot carry it.

README no longer calls the deployment Cloudflare Pages.

Verified against wrangler dev with the production _headers: /, /docs and
every asset return 200; /nope and /deep/nested/nope return 404 with the
site's own page, its assets loading and the CSP applied; both pages and the
404 render with no console errors, no failed requests, and zero horizontal
overflow at 320, 380, 480 and 768px. npm run validate passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
main added a wrangler.jsonc pointing `assets.directory` at `./dist`, which
is the compiled library — .mjs, .cjs, .d.ts and .css with no index.html —
so a deploy from it serves nothing at /. This branch's version, pointing at
`./site`, is kept.

The conflict is also why CI stopped running here: workflows triggered by
`pull_request` build the merge ref, and GitHub cannot compute one for a
conflicting PR, so no run was created for the last two pushes. Merging the
base back in restores it.

"$schema" is left out rather than carried over: it resolves into
node_modules, and wrangler is deliberately not a dependency of this
package — deploys invoke it through npx.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
The site advertised the library but barely demonstrated it. The landing page
had four hero buttons and the appearance playground; the "examples" section
was static code with a Copy button and nothing that ran. The full demo — the
one covering drawers, sheets, toasts, the palette, forms and wizards — was
demo/index.html at the repository root, which loads ../dist/ directly and is
never deployed.

site/demo.html runs all of it at /demo, against the same vendored build the
rest of the site uses: alert, confirm, prompt, validated prompt, open(),
stacking, animation options, drawers on both sides, bottom sheets, three
toast tones, the lightbox, loading states, the command palette, the
notification centre, form(), wizard(), and formWizard(). Each card pairs a
control that really runs with the snippet of the call being made, so the page
cannot drift from what it documents — a wrong snippet would show up as a
misbehaving button beside it. A plugin registered on the page logs afterOpen
and afterClose, and every demo prints its return value, so the result log is
also evidence the hooks fire.

demo.js uses a single delegated listener keyed by data-demo. The site's CSP
is script-src 'self', so inline handlers would be blocked; a demo that throws
prints the failure into the log rather than only the console.

The mark was only ever a favicon and an apple-touch icon: no page displayed
it. It now sits in the header of every page beside the wordmark, with alt=""
because the adjacent text already names the product and a screen reader
should not announce it twice.

The API calls were taken from the reviewed report in etc/ and the manual demo
page rather than written from memory, and the global surface was read out of
a real browser first, so nothing here calls an export that does not exist.

Verified in Chromium against wrangler dev with the production _headers CSP:
all 22 demo controls surface what they claim with no console errors and no
failed requests, the header mark renders on all four pages, the version badge
reads from the library's own export, and every page fits at 320, 380, 480,
768 and 1280px with zero horizontal overflow. npm run validate passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
PR #9 was squashed onto main as 631f43c, so this branch was restarted from
main and the demo work sits on top of it. The remote branch still points at
the pre-squash commits, whose content main already carries; recording them as
a parent lets this push fast-forward without discarding the branch history a
force push would have rewritten.

The tree is unchanged by this merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: deb67515-eab1-43cf-bb1e-bce3a6a0bef9


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
forge-dialog c83ce5a Sep 02 2026, 06:35 AM

Read against autumn.konexforge.com and forgeselect.konexforge.com — their
sources, since this environment's egress proxy refuses both hostnames — and
closed the gaps that were real rather than cosmetic.

The checker is the substantial one. ForgeSelect verifies its built site for
broken local links; scripts/check-site.mjs does that and the four other
things this site has actually got wrong: a fragment naming an id that does
not exist on the page it lands on, a link written in the .html form the
server answers with a redirect, JSON-LD that does not parse or an FAQPage
whose questions have drifted from the visible ones, and a page missing a
canonical, title, description or og:image — plus a 404 that declares a
canonical or forgets noindex, and any disagreement between pages about the
canonical origin. Each rule was verified by breaking the site and watching
it fail, not by watching it pass. It runs at the end of site:build, so a
broken site fails the Cloudflare deploy instead of reaching the domain.

Entity graph: an Organization node for KonexForge, referenced as the site's
publisher and the software's author. Both sibling sites are under the same
domain, and a shared @id is what lets a search engine treat them as one
publisher's products rather than three unrelated pages.

Positioning: the FAQ answered how the library differs from window.alert but
named no library. It now answers the comparison people actually search for —
SweetAlert2 and Bootstrap modals — factually: the native dialog element does
the focus and layering work, there are no runtime dependencies where a
Bootstrap modal needs Bootstrap, and an alert-only build is about 5.5 KB.

Metadata parity with the siblings: robots, author, explicit twitter:title,
twitter:description and twitter:image rather than relying on the og: tags
being inherited, og:image:type, rel=sitemap, and og:locale corrected from
"en" to the en_US form the property actually takes.

favicon.ico, rendered from the same icon.svg as every other icon: a 32×32
PNG in an ICO container, which needs no bitmap encoder and costs 1.2 KB.
Clients request that path whatever the markup says. build-icons.mjs writes
it alongside the other two and reproduces all three byte-for-byte.

Deliberately not adopted: meta keywords. Both siblings carry one and Google
has ignored it for years; the competitor terms belong in the visible FAQ
answer, where they are also true, and that is where they went.

Verified in Chromium against wrangler dev with the production _headers CSP:
all four pages carry the new tags in the live DOM, /favicon.ico serves as
image/vnd.microsoft.icon, the four schema types on the landing page parse
with Organization among them, and every page still fits at 320, 380 and
768px with zero horizontal overflow and no console errors. npm run validate
passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4
Autumn Note ships its site in English and Vietnamese under /vi/; this one
did not. All three pages now do: /vi/, /vi/demo, and /vi/docs, translations
of the English pages rather than different content, mirroring them section
for section and keeping the same element ids so the scripts drive both
languages.

Nothing user-visible stayed hard-coded in those scripts. site.js, demo.js
and common.js now read their strings from a JSON data block in the page,
falling back to English — a data block rather than an inline script,
because the site's CSP is script-src 'self' and real inline code would be
blocked. The block's id is prefixed: "i18n" alone is already a section id
on the docs page, and getElementById would have found that section instead.
Nothing broke on that today, since the docs page loads neither script, but
it was a trap set for whoever added one.

common.js also passes the page's `labels` to the library's own setLabels(),
so a dialog opened from a Vietnamese page says Huỷ and Gửi rather than
Cancel and Submit. The theme toggle and copy buttons read their four
strings from data- attributes for the same reason. The code shown beside
each demo is translated too: the snippet has to be the call actually made,
or the page stops documenting itself.

hreflang: every page declares en, vi and x-default, and the sitemap repeats
the set as xhtml:link alternates on all six entries, as Autumn's does.
Assets on a /vi/ page are linked root-relative, because the page sits a
directory down and "./" would resolve inside /vi/.

check-site.mjs gained the hreflang rules, and they earned their place
immediately: writing this, both /vi/demo and /vi/docs shipped a language
switcher still marking EN as current, because the replacement that flips it
silently matched nothing. The checker now fails on a one-way annotation, a
missing x-default, and a page whose hreflang set omits its own canonical —
each verified by breaking the site and watching it fail. None of the three
is visible in a browser.

Verified in Chromium against wrangler dev with the production CSP: all six
pages return 200 with the right lang attribute, the switcher marking the
right side, the localised theme label, the mark rendered, and the version
badge read from the library's own export — with zero console errors, zero
failed requests, and zero horizontal overflow at 320 and 768px. The
Vietnamese demo opens a Vietnamese dialog with Vietnamese buttons while the
English one is unchanged. npm run validate passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4

cmm-cmm commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

The red e2e on 9e33dad was not this PR's, and it is intermittent

Recording it rather than leaving a red X unexplained. CI is green on the current head (c83ce5a, 11/11).

What failed — e2e, WebKit only, e2e/dialog.spec.ts:67:

appearance builder applies scoped styling and keeps dragging inside the viewport
expect(moved.x + moved.width).toBeLessThanOrEqual(viewport.width + 1)
Expected: <= 1281
Received:    1282.9571533203125

The dragged dialog's right edge landed 2.96px past a 1280px viewport, against a 1px tolerance. Chromium and Firefox passed.

Why it is not this PR's. The test drives demo/index.html. demo/, src/, e2e/, and styles/ are byte-identical between this branch and 22c0c65, where the same job passed — this PR only adds files under site/ and touches scripts/, README.md, and docs/website.md. Nothing it changes is loaded by that test.

Not reproducible, and my first explanation was wrong. I guessed the hover scale: 1.02 was re-applying after mouse.up() and pushing the dialog out. Measured instead of assumed: the dialog is 420px wide, so that scale would add 4.2px, not 2.96px — and in Chromium the computed transform after the drag is none with 0.000px of overflow. So that is not the mechanism. WebKit is not available in the environment I am working in, so I could not reproduce it directly.

It failed all three Playwright attempts within run 33597461244, then passed cleanly on the next commit with the same test inputs. That points at something timing- or subpixel-dependent in WebKit's handling of the drag clamp, not at a deterministic bug.

No fix pushed, deliberately. Any real fix is in src/ drag clamping or in the test's tolerance, and this PR adds a demo page and an icon — widening it into library behaviour on a failure that is green on the head would be the wrong trade. It is worth its own issue: the clamp appears to be able to leave a dialog a few pixels outside its bounds under WebKit, and 0.7.0 claims a hover transform can no longer do that.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MtJ3nDatGiv7dHjvZL1uZ4


Generated by Claude Code

@cmm-cmm
cmm-cmm marked this pull request as ready for review September 2, 2026 06:43
@cmm-cmm
cmm-cmm merged commit 19a9303 into main Sep 2, 2026
12 checks passed
cmm-cmm pushed a commit that referenced this pull request Sep 2, 2026
PR #10 was squashed onto main as 19a9303, so this branch was restarted from
main and the package split sits on top of it. The remote branch still points
at the pre-squash commits, whose content main already carries; recording them
as a parent lets this push fast-forward without rewriting history.

The tree is unchanged by this merge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants