Registro de cambios
Qué cambió en cada versión de la interfaz compartida y las tareas, a partir de CHANGELOG.md en la raíz del repositorio.
Changelog
All notable changes to the shared UI package @joeblew999/remy-ui are documented here.
The format follows Keep a Changelog and the
package follows Semantic Versioning.
Unreleased
Changed
- GitHub's verdict on a commit is main's push run: a release tag's run on the same commit (the release
job's) no longer holds
dev:promoteas "still checking".
[0.15.1] - 2026-10-11
Changed
remy docs publishis TypeScript in the command line (the threedocs/scripts/*.mjsare gone) and waits for the index's sync it started before asking the fixed questions;remy docs questionsasks them alone. On 2026-10-11 the questions ran seconds after the sync started, against the old index.remy.config.tsalone sets the origins and the release (DEPLOY_ORIGIN,STAGING_ORIGIN,DOCS_ORIGIN, theirVITE_*,RELEASE_*): a value left in the shell by another project is removed, where it used to win. The ports still take a value given in the shell.- GitHub's verdict on a commit (
dev:status,dev:change,dev:promote) is its push run's; a Dependabot run on the same commit no longer reads as green. - The check's route tree part also reruns when
src/start.tschanges (the tree registers the Start instance's types). @joeblew999/remy-showcase0.1.1 declares what it imports, at the platform's exact versions (a unit check holds them equal;ui:versionkeeps them so): in an app whose lock put zod 3 at the top ofnode_modules,zod/miniresolved to it and the build failed.@joeblew999/remy-auth-contract0.4.1 (its peer floor).
[0.15.0] - 2026-10-11
0.14.0 was prepared on main and never published; its changes ship here.
Changed
- No mise.
package.jsonis the entry: its scripts run every command (npm run dev:status|start|change|land|promote|release|done,npm run dev|build|preview|test|setup|packages|docs|…). The tooling isremy, the package's bin (packages/ui/src/cli, TypeScript; an app runs the JavaScript emitted from it), withremy.config.tsfor an app's origins, ports and release package and.node-versionfor Node. The shared workflow runs onsetup-nodeandnpm run; the blank app has nomise.toml; an app moves to a release withnpm run upgrade -- <version>. The deploy's wait ends only when the home page's every asset answers.
[0.14.0] - 2026-10-09
Added
-
Which version is deployed (remy-sport's build stamp, for every app).
build/vite:remyBuild(), inremyApp()andremyDocs()already, works out a stamp per build from the sources (the app's package, the commit, a hash of anything uncommitted, the platform packages' versions) asvirtual:remy-build. Every Worker's/healthzanswers with it, itsenvironmentand Cloudflare'sdeployedAt, readable from any origin.versions:BuildStamp, now in the app frame's footer (the environment off production, the product's name, the commit, a reload control when the deployment has moved on under an open page), andVersions(this app, its docs Worker and other listed deployments, each answering for itself).build: the shapes and TanStack Query options.mise run cf:versions: a row per deployment, each commit placed against the checkout. Checks in every app's shared set (versions.checks,build.checks). -
Staging:
mise run cf:stagingdeploys the Wrangler configuration'senv.staging(built withCLOUDFLARE_ENV=staging) toSTAGING_ORIGIN;cf:deployand its provisioning guard read the environment being deployed, andcf:versionsasks staging too.defineRemyApp'sdeployments: the app's deployments, whichVersionslists.mail'sunreachable(address): a message to a reserved test or example domain is never sent. -
The product's name is the app's, written once (
defineRemyApp'sbrand):pageTitle, pageHead'stitleanddescriptionreceive it as a second argument, andproductNameChecks(every app's shared set) fails a page of an app with another name that says "Remy". The showcase's Settings page shows where the name is used and how another would read (ProductNameCard). -
app-config:defineRemyApp(the app's brand, source, and site and app navigation, each link written with TanStack'slinkOptionsso it is checked against the app's own routes),NavItem,useRemyApp,usePreferredLocale. The shared frame names no route but/, so an app with other pages than remy-auth's type-checks and shows only its own. -
app-shell:AppShellalone, without the showcase pages. -
@joeblew999/remy-showcase/app-nav:showcaseSiteNav,showcaseAppNav, the showcase's lists for apps that show it. -
docs/*: the docs Worker itself (Fumadocs on TanStack Start and Cloudflare):docs/vite'sremyDocs(docsConfig, { contract })builds an app's docs from itsdocs/content/anddocs.config.ts, generating the Worker's Cloudflare configuration intodocs/.remy-docs/;docs/config'sdefineDocsConfig;docs/tsconfig.jsonfor an app's one-linedocs/tsconfig.json. Ask AI is opt-in (askindocs.config.ts); an app without an API (contract) has no reference. -
skills/remy: the platform's rules as an agent skill, generated from the developer docs; the sharedskills:installinstalls it,agents:rulespointsAGENTS.mdat it. -
defineRemyApp'sdocs: the docs Worker's origin; the frame shows the Docs, Developers and Guide links. -
app/vite:remyApp(), an app's wholevite.config.ts(TanStack Devtools, the parts, Cloudflare, Fontaine, Tailwind, Start, React), compiling the app's own catalog when it hasproject.inlang. -
root(remyRoot,RemyRouterContext,preferredLocale) androuter(remyRouter): an app's__root.tsxandrouter.tsxare one call each. -
locale'sfollowLocale(runtime): a second Paraglide catalog (an app's or a package's) in the platform's language. -
The CSP report route
/csp-reportis the package's, mounted beside every app's routes;startMiddlewareenforces the policy and reports there by default. -
Zod's
jitlessis set once for every app (AppProviders) and the docs Worker. -
Tasks:
packages:check,packages:upgrade,packages:pack,packages:release,packages:tag,packages:publish,packages:notesfor any repository that publishes packages (its non-private workspaces;RELEASE_PACKAGE,RELEASE_TITLE);project:release-checks(a hook),project:doctor,project:verify-tooling(lockfile,wrangler.jsonc's observability andpreview_urls: false, skill pins),project:single-copies(inproject:check),project:preconditions(inproject:setup),project:refresh-tasks. -
CI:
.github/workflows/google.ymlis a reusable workflow; an app calls it at its tag, andproject:upgrade-uimoves that tag with the package and the tasks. -
i18n:messages:checkfails when an app's catalog lists other languages than the platform serves. -
project:test:consumers: a hook tiers 3 and 4 and CI run last, for the other repositories a repository serves (remy-auth:template:test, the consumer fixture infixtures/consumer/). -
packages:publish --dry-run;packages:publishfirst checks that each registry accepts its token (npm whoami) and stops on any answer but "not found", before anything is published. -
template/: the blank app a new repository starts from (identity, one home page in every language, a docs index per site);docs:inittakestemplate/docs. -
api/guard-core: the guard, free of any oRPC import so the same code runs on oRPC 1 and 2: who may call a procedure is itsmeta.policy(publicorsession),guardMiddlewareenforces it as the router's root middleware, and a procedure that declares none never runs.guardProblemsnames a procedure with no policy, with no guard in front of it, a public one whose response names a person (an email address, say), and a personal one that does not say who receives it (meta.personal). -
api/guard:guard()(the guard for the installed oRPC, an app router's root middleware),GuardContext(getSession, as oRPC's Better Auth guide shares a session),once,signedIn,noSession, and everything inapi/guard-coreandapi/policy. -
api/policy:policy()andpersonal(), the oRPC metadata a contract procedure declares for the guard, andactions(vocabulary)for policies that are actions of the app's relation vocabulary. -
api/relations: the relation engine, lifted from remy-sport and taking an app's vocabulary as data (defineVocabulary,relationEnginewithcan,canAll,canFor,holds,heldAmong,objectsHeldBy,usersHolding,audienceFor;vocabularyProblems,schemaProblems). The guard enforces an action policy with it: 401 without a session, 404 for a missing object before 403. -
environment:environments(), an app's table of what each environment permits; anything undeclared is production.mail:mailerFor, mail through Cloudflare Email Service or kept in the Worker's outbox where the environment captures it. -
allowed:<Allowed can action>, which shows a control only when the server allowed that action for this viewer on this object;allowed.checks:offeredActions,allowedActions. -
api/server:apiHandlersanswersno-storeto a request with credentials, and on every 401. -
@joeblew999/remy-showcase/app-pages:AccountPagetakesauth(who is signed in, and the app's sign-in and sign-out controls); without it, the empty state as before. -
@joeblew999/remy-auth-contract0.4.0:me(GET /api/me, the signed-in person's own account; 401 without a session), the notes demo (notes.*,notesVocabulary,./notes) anderrorStatuses; written for oRPC 2.ui:versionsets its peer floor for@joeblew999/remy-uito the release. -
Tasks:
project:prepare, a hookproject:devandproject:buildrun first (nothing by default);plans:open, a parked plan taken up again.
Changed (breaking)
- Every message that named the product takes it as
{product}:home_title,home_description,home_intro,home_shared_heading,home_shared_intro,home_developers_text,formats_description,app_home_description,account_description,account_empty_description,account_sign_in_description,email_code_subject,email_code_intro. Pass{ product: useRemyApp().brand }; TypeScript names each caller.showcaseChecks,formatsChecksandpreloadChecksneedproduct. WithoutAppProvidersthe frame's brand is empty, no longer "Remy". Three messages nothing used are gone:ask_description,ask_intro,search_description. - oRPC 2: every
@orpc/*dependency is 2.0.0-beta.42 (@orpc/openapi-clientis gone, merged into@orpc/openapi). An app's contract writes its routes as.meta(openapi({ method, path, ... }))and its policy as.meta(policy('public'))(api/policy;ApiMetaandoc.$metaare gone); its errors carry nostatus, and the API's own error codes get theirs fromerrorStatuses, passed toapiHandlers,generateSpec,apiChecksandcoverageProblems. An error's body no longer holdsstatus.contractClient(contract, { origin })replaces{ url }.api/coveragereads a route withrouteOf(procedure). A client still on oRPC 1 reads a successful answer but not a typed error. apiChecksnow fails an API whose procedures are not behind the guard. An app with an API putsguard()(api/guard) at its router's root (implement(contract).$context<GuardContext<...>>().use(guard())) and gives each call agetSession()in its context; an app that signs nobody in passesnoSession.- Tasks:
cf:deployandcf:previewrefuse when a D1, KV or R2 binding names no existing resource, instead of letting Wrangler create one during the deploy. - One dependency set: the framework, toolchain and the tools the shared tasks run are the package's exact
dependencies (no more optional peers); an app's
package.jsonnames@joeblew999/remy-uiand drops its own pins of them, orproject:single-copiesfails. - No remy-auth page list is a default:
localizedWorker'sentryPaths,entryRedirect,sitemapXml'spathsandprerenderPages'pathsare required; the sitemap listsdefineRemyApp'ssitePaths. - The shared check sets take the app's
sitePathsandappPaths; the home page's words are checked only when the app passeshome. - An app's own
src/routes/csp-report.ts,src/csp.tsandsrc/preferred.tsgo (the package's route, defaults androotreplace them). defineRemyApptakesserviceandsitePaths(required: the sitemap lists them) and registers the app (registeredApp);pageHead's titles and the home page's WebSite data use itsbrand, where they used 'Remy'.startMiddleware'scspis optional. The check sets'ownSitePaths,ownAppPaths,formatsanddevicePathoptions are gone (sitePaths,appPaths,showcase).- Tests are type-checked: an app's
tsconfig.jsonincludestests/(the blank app's does).HomeContent's functions take aLocale. - remy-auth's showcase pages are a package of their own,
@joeblew999/remy-showcase(its README):pages,app-pages,app-nav(wasshowcase/app-nav),clock-route,reservation,search-params,navigation-blocking, the showcase'spathslists and checks (showcase.checks,reservation.checks, withreservationApiChecksfromapi/checks). The platform'spathskeepsisAppPath. The parts' own pieces moved into their parts (below);build-boundaries.checksandcode-splitting.checksare at the root;rowsandzod-cspare exported. - Parts from other packages: an app lists another package's part as
<package>/<name>insrc/parts.json; the package offers them in itsparts/catalog.json, each part's folder with achecks.jswhose default export runs its checks. The showcase's parts (time-zones,deferred-place,status-card) moved there with their pieces (@joeblew999/remy-showcase/parts/time-zones/page,parts/deferred-place/device-place,parts/status-card/card,parts/status-card/query): list them as@joeblew999/remy-showcase/<name>. The time-zones breadcrumb's middle link is the app'sparentoption (src/parts/time-zones.ts).problemCheckslosttimeZones(the part's own checks now);partCheckslostrootandfile. The platform's own part isseo-routes. - The showcase's checks are one call an app showing the showcase makes itself,
showcaseChecks()(@joeblew999/remy-showcase/showcase.checks, withdemoChecks,appNavChecksandformatsChecksfromchecks);serverAppChecksandprerenderedAppCheckslostshowcase.problem.checksis at the root. @joeblew999/remy-auth-contract0.3.0 imports the reservation from@joeblew999/remy-showcase(peer: remy-showcase 0.1.0 on); 0.2.1 needs remy-ui below 0.14.packages:bumped(first inpackages:release, and inpackages:tag): a published package whose files changed since the last release tag must have a new version, or the release stops.buildBoundaryCheckschecks a page's device time only withdeviceTimePath(the showcase's/formats).project:verifyalso runsproject:doctorandproject:verify-tooling;project:generatecompiles the app's own catalog by default. A missingsrc/parts.jsonlists no parts.AppProviderstakesapp(adefineRemyAppconfig) andpreferred(the root loader's language) in place ofrepository; every frame reads both, so pages no longer passpreferred.SourceLink,SiteNavLinksandAppNavLinksare gone: the config'srepository,site.linksandapp.linksreplace them../preferred(usePreferred) is gone: the root passespreferredtoAppProviders.Intro's andFormatsContent'sbackTois'site' | 'app'(the app's home comes from the config).- The sidebar and the phone's bottom bar list the config's
app.nav, no longer remy-auth's pages; an app showing them passesshowcaseAppNav. - An app's docs/ is
content/,docs.config.tsand three one-line files (docs:initwrites them); itswrangler.jsonc,src/,scripts/,tests/andplaywright.config.tsgo. The shared docs tasks readdocs/.remy-docs/wrangler.json;docs:publishand the questions check readdocs/content/questions.json. mdast-util-to-markdownis pinned at 2.1.2: 2.1.3 overflows the stack on bold text in Fumadocs' build.
[0.13.0] - 2026-09-26
Added
providers:AppProviders, what an app's root renders its pages in (reading direction, theme, source link), so no app misses one (remy-auth-app's theme toggle did nothing without the theme provider).checks:themeChecks(choosing Dark darkens the page), in both app check sets.clock-route:clockRouteOptions,clockDefaults,clockZonesfor an app's Clock route.
Changed
project:checkbuilds before it type-checks, so a new route's generated route tree is current.
[0.12.0] - 2026-09-26
With @joeblew999/remy-auth-contract 0.2.1 (unchanged code; its peer range is now >=0.12.0, so a
release changes one version number, not four).
Added
- Formats page, one shape in every language (issue #5): every section two columns, so none ends with a
hole; notes in their card's footer (
Group'snote); each language offered its own digits too (ownValues().numberingSystemsadds the numbering systems written in its script: Thai, Devanagari, Han, Arabic-Indic), so no language is Latin-only; sorting shows names in every script;NameListisolates each name in a mixed-script list.FormatsExtraschanged:numbersis rows in the Numbers card,moneyis gone andcurrencyNoteis the currency card's footer. shell:SourceLink, a context for the site header's "GitHub" link. An app sets its own repository (<SourceLink value="https://github.com/you/app">); without it the header shows no source link instead of remy-auth's.- Translation tasks in two pipelines (
tasks/i18n/):i18n:messages:checkandi18n:docs:check(offline, read-only; git, jq,@lingual/i18n-check0.9.5, a plural-category check),i18n:checkboth (a warning; strict withI18N_STRICT=1), andi18n:messages:translate,i18n:docs:translate,i18n:translate: Claude Code 2.1.282 pinned in the task, with no tools, on main under a lock shared by every worktree, committing what it translated. Replacesi18n:status, the oldi18n:translateand the provenance lines. Plural messages use=*for their fallback variant. - Mobile navigation (
.plans/done/mobile-navigation.md): on phones (belowmd, 768 px) a bottom bar (blocks/bottom-nav, an owned block of stock shadcn parts: shadcn has none) with the core pages and More, which opens the sidebar as its sheet; on tablets and desktops the sidebar, now collapsing to icons (collapsible="icon"). One list feeds both:app-nav.tsx(appNavItems,matchesNav). - App pages
ClockPage(time now in chosen zones; the route keeps them),SettingsPage(language, appearance, the device's zone and languages) andAccountPage(an empty state until sign-in);appPathsgains/app/clock,/app/account,/app/settings, so an app on the package adds those three routes when it updates. New catalog keys are English only while translation is frozen. checks:appNavChecks(the phone bar, More, the desktop sidebar, the clock's zones), run by bothserverAppChecksandprerenderedAppChecks.checks:fontChecksenforces a font byte budget: a first visit to each site page downloads at mostfontBudget(300 KB, optionbudget) of fonts, in every language. Measured on a local production build: 28.7 KB (Latin) to 227.2 KB (/ar/formats) (.plans/fonts.md, step 1). New exportsfontBudgetandsystemFontScripts.- Shared tasks
i18n:status,i18n:checkandi18n:translate(tasks/i18n/): per locale, docs translations missing or stale against the English version recorded in their first line (<!-- translated-from: <path> @ <blob sha> -->), and catalog keys missing, extra or with other placeholders than the base locale.project:checkrunsi18n:checkas a warning;ui:releaseruns it strict first. An app with nothing to translate gets "nothing to translate". Replaces remy-auth's heading-onlydocs:translations.
Changed
fonts.css: Japanese and Traditional Chinese draw with the system's font for their language (Chrome picks it by the page'slang: Hiragino Kaku Gothic ProN and PingFang TC on macOS). Their Noto web fonts cost 351 KB (/ja) to 1,176 KB (/zh-TW/formats) per first visit; now 0 bytes beyond Geist. The dependencies@fontsource-variable/noto-sans-jpand-tcare removed, and the build's font files drop from 496 to 25.fonts.css: Persian draws with Vazirmatn (@fontsource-variable/vazirmatn5.3.0, a Persian-designed face; fontsource has no Persian Noto) instead of Noto Sans Arabic:/fadownloads 74 KB of fonts instead of 191 KB,/fa/formats108 KB instead of 259 KB. Arabic keeps Noto Sans Arabic.checks:fontCheckslets a system font draw a Han page, never tofu (LastResort), and its Han check compares the fonts that actually draw each language's heading instead of the names in the CSS, so Japanese and Traditional Chinese drawn by one font still fails. Every other script still needs its web font.
Changed
checks:cspChecks({ enforce }), defaulttrue: expects the nonce policy underContent-Security-Policy(and none underContent-Security-Policy-Report-Only), or the reverse withenforce: false; checks the not-found page's policy too; enforced, proves a script without the nonce is blocked and reported.serverAppChecks({ cspEnforced })passes the app's switch. An app still sending the policy report-only passescspEnforced: false.
[0.11.0] - 2026-09-25
Added
- Moves that remove copying from consumers (
.plans/publisher-consumer-analysis.md, D4, D8, D9, D12):./tailwind.css(globals.css,fonts.css,text.cssand the package's own@source, so an app writes one import and its own@source "../src");./prerender(prerenderPages({ notFoundPath }), a prerendered app's page list);seo'ssitemapXml({ origin, paths, extra }),sitemapEntries,robotsTxt(origin),sitemapType,robotsType;./app-checks(serverAppChecks,prerenderedAppChecks: the shared check set in one call per kind of app, the app's own pages passed beside the shared ones). remy-auth uses them; its CSS, sitemap, robots.txt and registered checks are unchanged. The separate imports keep working. showcase/status-card: the live status card (StatusCard,LiveStatus,statusRefreshMs), moved from remy-auth so remy-auth-app shows it too. The app passes the query: its own client'squeryOptions(server-rendered), or acontractClienton another app's origin withserverRendered={false}(asked by the browser, with its own note, messagelive_status_note_browser). An answer that breaks the contract, or none, shows as an error (data-status="error"), never as data../invalidate:invalidateEverything(router, queryClient), moved from remy-auth (it took the router only and read the QueryClient from its context).@tanstack/react-queryis a new optional peer.api/server:apiHandlerstakesorigins, the registered apps' exact origins allowed to call the API from their pages (oRPC's CORSPlugin; none by default, a wildcard is refused).api/checks:apiChecks({ origins })checks CORS: each registered origin allowed on a simple call and a preflight, any other origin not.showcase/status-card.checks:statusCardChecks({ origin, registered })for a consumer that asks another app's contract across origins (no status in the prerendered HTML, the other Worker names the page's origin, nothing asked from an unregistered origin), and for every card on a contract endpoint a check that an answer breaking the contract is shown as an error.- Parts (
.plans/parts.md):./parts(readParts,catalog),./parts/vite(remyParts(): a Vite plugin generatingvirtual:remy-partsfrom the app'ssrc/parts.json, and the route config that mounts each listed part's routes through TanStack'svirtualRouteConfigandphysical()),./parts/checks(partChecks()). First part:time-zones(the/time-zones/$route and its checks), so an app adds or removes it with one line. New dependency@tanstack/virtual-file-routes. - Parts, second pass: three more parts, each one line in
src/parts.json.deferred-place(Cloudflare's place streamed into the formats and location pages, itsgetPlaceserver function and checks; links a zone totime-zonesonly when that part is listed),seo-routes(/robots.txtand/sitemap.xml, listing the package's site pages, every listed part's and the app's own entries from itssrc/parts/seo-routes.ts) andstatus-card(the live status card on the app home, with the app's status query from itssrc/parts/status-card.ts). The mechanism gains entry modules (virtual:remy-parts/<part>/<entry>,undefinedfor an unlisted part), app options (virtual:remy-parts/<part>/app), site paths a part adds (sitePaths) andpartSitePaths(). New exports./parts/seo-routes/sitemapand./parts/status-card/query(types for the app's options). One implementation each: the seo-routes part builds its routes withseo'ssitemapXmlandrobotsTxt(as a prerendered app writes its files), and the status-card part isshowcase/status-card's card with the app's query. serverAppChecks({ parts })is part-aware: what a listed part owns runs withpartChecks()instead, never twice (withseo-routesno sitemap test inpublicPageChecks; the device-place row expects Cloudflare's place only withdeferred-place).partsdefaults to the app'ssrc/parts.json, or none without one;prerenderedAppChecksis unchanged. remy-auth's test file is oneserverAppCheckscall, onepartCheckscall and its own checks.sitemapChecks({ paths, oneLanguage })(checks): the sitemap test, split out ofpublicPageChecks, which runs it unless toldsitemap: false(the seo-routes part's checks run it then). The 404 check it shared a test with is a test of its own, still inpublicPageChecks../problem(Problem,NotFound,ErrorPage,problemPages) and./preferred(usePreferred), moved from remy-auth so parts' routes can use them. Nothing changes for existing imports../smoke:smokeChecks({ sitePaths, appPaths, hydrate, locales }), tier 1 of the test tiers (every page answers, site pages with a heading, chosen pages hydrate cleanly) for any app on the package.shell: the site frame (SiteShell,Shell,SiteNavLinks,Intro,SkipLink,ZoneBadge) in a module of its own, so a page that needs only the frame (remy-auth's docs and problem pages) no longer downloads the home and formats pages.pagesre-exports all of it: nothing changes for existing imports.publicPageChecks({ oneLanguage: { translations } }): one-language pages that also have their own text in other languages (remy-auth's translated docs) are expected in the sitemap once per language, self-canonical, with those languages as alternates and x-default.locale-data(plain JavaScript, re-exported bylocale-info): one module that derives, per locale, its region, script, currency, calendars, numbering systems and plural counts (ownValues), each control's choices over all of Paraglide's locales (allChoices,choicesFor: the page's language's own first) and the formats page'ssearchDefaults(from the base locale). The region's currency comes fromcountry-to-currency(new dependency; the runtime has no currency-for-region API).- A numbering-system control on the formats page (
?numbering=), in the Numbers section. LocaleInfohasregion,currencyandcounts;Grouppasses other props (data attributes) to its card.fontChecks({ paths })(checks): per language, the fonts that actually draw the heading and intro (Chrome DevTools ProtocolCSS.getPlatformFontsForNode) are the onesfonts.cssnames for it; a system or fallback font drawing the text fails naming the page's script and the font to add, a named script font that draws nothing fails, and Japanese and Traditional Chinese must name different fonts.
Fixed
fonts.css: Arabic, Persian and Hebrew pages are drawn with Noto Sans Arabic and Hebrew on macOS and Windows. Each language's script font now comes before fontaine's Geist fallback (local Arial, which carries Arabic and Hebrew and so drew them itself), followed by the script font's own metric-matched fallback face; Geist's fallback stays last. No new preloads.
Changed
- Formats page: every section opens with what it is for the page's language (
data-own-area): Money shows the amount in the language's own currency (was euros), Words lists the language's plural forms. Every control offers the union over all locales, the page's own values first, in the secondary look and described by a "This language" badge.currencies,countChoicesand the showcase calendar list are gone; the currency default is the base locale's (USD, was EUR) and the count default its first count above one in the general form (2, was 3). formatsChecksandsearchParamsChecksiterate the derived values, so a new locale needs no check edit; they also check the "for this language" card of every section and each control's order and marking.- The formats rows (
Group,Row) live in their own module and the showcase modules whose route options run in every page's first load (search params, time zones, device place) import them and the frame from there instead of frompages.pagesstill exports them.
Added
SiteNavLinks(pages): a context through which an app adds its own links to the site header; remy-auth adds "Docs". Without it the header is unchanged.publicPageChecks({ oneLanguage }): site pages written in one language only, listed once in the sitemap without alternates and checked on a narrow screen like the others.- shadcn's
tablecomponent (throughui:components), and messages for the docs and the answer page in every language.
Changed
publicPageChecks: the narrow-screen, fonts and mirrored-header check runs one test per language, and one-language pages once, in their own language.
[0.10.5] - 2026-09-25
Added
- Ten more languages: fa, he, th, ja, zh-TW, hi, am, pl, tr, de (13 in all), each with its Noto
font from fontsource where Geist has no glyphs, applied by
:lang(). text.css: hyphenation by the page's language, casing bylang(Turkish İ), phrase breaking for Japanese headings.checks:textChecks({ paths })(every page fits 320 px, per language).- Formats: each language's own calendar and digits through one explicit format tag
(
formatTag), more calendars in the control, week rules fromIntl.LocalegetWeekInfo, word segmentation withIntl.Segmenter, native digits in counts through Paraglide's number function. matching.js: a Paraglide custom strategy that sends Chinese browsers to zh-TW.- Quick test tier (
project:test:quick): one language per writing system (QUICK_LOCALES, default en, ar, ja, th).
Changed
- Per-language checks (app pages kept out of search, CSP violations while hydrating, text) run as one test per language, so the time per test does not grow with the language count.
0.10.4 - 2026-09-25
Added
-
Site header on shadcn's NavigationMenu (plain links, complete without JavaScript), a footer list of every language as real links, and a header language dropdown over the same links whose trigger falls back to that list without JavaScript.
-
theme: shadcn's TanStack Start ThemeProvider and ModeToggle (light, dark, system), on site and app pages. Without JavaScript site pages show the default theme. -
Breadcrumb on the time zone pages (shadcn Breadcrumb, server-rendered links).
-
tanstack:pageHeadadds schema.orgWebSitestructured data (name, site root) to the site home page through TanStack'sscript:ld+jsonhead entry. -
worker: every response sendsContent-Security-Policy: frame-ancestors 'none',Cross-Origin-Opener-Policy: same-origin-allow-popupsandStrict-Transport-Security: max-age=300. -
checks:cspChecks({ paths, reportPath })for a nonce-based Content Security Policy (nonce on every script, no violations while hydrating, report endpoint);publicPageChecksandzoneCheckscover the structured data;observabilityCheckscovers the new headers. -
./api/server,./api/client,./api/coverage,./api/checks: contract-first APIs on oRPC 1.15.4 (.plans/openapi-contracts.md).apiHandlersmounts an oRPC router in one TanStack Start server route with the generated OpenAPI 3.1 document (/api/openapi.json) and its reference page (/api/doc);contractClientcalls any contract with every response validated;isomorphicClientgives an app one client for its own contract on both sides;coverageProblemsandapiChecksfail on a procedure without a route, a policy or documented errors;reservationApiCheckscovers the demo reservation over HTTP. New dependencies:@orpc/client,@orpc/contract,@orpc/openapi,@orpc/openapi-client,@orpc/server,@orpc/zod, all 1.15.4;@tanstack/react-startis a new optional peer. -
./reservation:reservationFieldErrorsandreservationConfirmation, the wire shapes a contract declares for a rejected and an accepted reservation. -
Paraglide's
routeStrategieskeep/api/*out of URL localisation: no redirect, and the language comes from Accept-Language, else the base locale.
Changed
- One zone badge style (secondary) on site and app pages; the app pages no longer repeat it as a label; site pages have no back links (they navigate through the header).
./reservationsets Zod'sjitlessoption, so Zod never probes for eval under a strict CSP.zoneChecksaccepts anonceattribute on the app pages' robots meta.showcase/status-card.checks:statusCardCheckstakesendpoint, the path the card asks for the status; without it, any server function as before.
0.10.3 - 2026-09-25
Added
./reservation: the demo reservation's rules as one Zod 4 schema (reservationSchema(locale), localized messages) for the browser and the server, the server function's input shape (reservationInput) andreservationErrorsfor a server's field errors.
Changed
showcase/search-params: the formats search params are validated by a Zod 4 schema,formatsSearchSchema, which TanStack Router takes directly asvalidateSearch(Standard Schema, no adapter). It replaces the hand-writtenvalidateSearchfunction and its parsing helpers;FormatsSearchis the schema's output type. Written with Zod Mini to keep the entry chunk small. New dependency:zod4.6.5.DemoPage's form runs on TanStack Form (@tanstack/react-form1.33.5) with shadcn's Field pattern: the shared schema validates on submit, a server's field errors show as the form's own, andonDirtyChangefollows the form'sisDirty. Its own validation code and input tracking are gone;ReservationandReservationResultnow come from./reservation(still re-exported).
0.10.2 - 2026-09-25
Changed
- Formats in five sections (this language, dates and times, numbers, money, words) with a list of
links to them; each search-param control sits in the section it changes.
FormatsExtrasslots are now rows in groups (language,systems,dates,currency) and cards per section (time,numbers,money,words);FormatsControlsandPrerenderedFormatsControlsbecomechoiceCardsandprerenderedChoiceCards(oneChoiceCardper param, withto). - Full width in both frames, as shadcn's sidebar-16 block has it; formats cards two per row on medium screens and three on wide ones.
0.10.1 - 2026-09-25
Added
- Formats as a site page and an app page:
FormatsContent(the page's content once),FormatsPage(site frame) andAppFormatsPage(app frame);/app/formatsinappPathsand the app sidebar;FormatsControlstakes the page it belongs to (to). LanguageMenu: the app pages' language picker, shadcn's DropdownMenu with a radio group; choosing calls Paraglide'ssetLocale. Site pages keep the plain links.zoneChecks: on a phone in landscape, the way back to the site stays in view.
Changed
- The app sidebar's "Back to the site" moves into
SidebarFooter, so it stays in view on short screens. performanceChecksjudges the median of five runs (Lighthouse'scomputeMedianRun).demoChecksswitches language through the app's menu.
0.10.0 - 2026-09-25
All in on shadcn, and site pages kept apart from app pages. Breaking for consumers.
Changed
- shadcn's monorepo layout: the app's
components.jsonroutesshadcn addinto this package; the stylesheet isglobals.css(wasstyles.css), exactly what the shadcn CLI writes (default Nova style, neutral theme, Geist), checked bymise run ui:verifybefore every release. - Components regenerated with shadcn's RTL mode;
direction(DirectionProvider),skeleton,empty,sidebar,sheet,tooltip,breadcrumb,collapsible,dropdown-menu,avatarand theuse-mobilehook added through the CLI. fonts.css: Geist from shadcn, Noto Sans Arabic on Arabic pages per shadcn's RTL guide, generic code font; apps add fontaine's size-matched fallbacks in their Vite config.paths:sitePaths(for Google: no JavaScript needed, indexed, in the sitemap),appPaths(under/app: JavaScript, noindex) andallPaths, replacingpublicPaths;pageHeadadds noindex to app pages.pages:SiteShell(static shadcn parts),ZoneBadge,SkipLink,Intro; the demo moves toapp-pageswithAppShell(shadcn's sidebar-16 block, owned inblocks/sidebar-16),AppHomePageandLocationPage.publicPageChecksenforces that every named font is loaded; newzoneChecks; checks find page links inside#main;codeSplittingCheckstakes a start page per zone.
0.9.3 - 2026-09-25
Added
showcase/device-place:DevicePlace, the device's own location from the Geolocation API, asked only when the visitor presses its button, explained before asking, never sent anywhere; with Cloudflare's location it also shows the distance between the two.devicePlaceChecks.cloudflare:Placecarries Cloudflare'slatitudeandlongitudewhen valid.worker: every response sendsPermissions-Policy: geolocation=(self), camera=(), microphone=(), asserted byobservabilityChecks.
0.9.2 - 2026-09-25
Added
showcase/search-params:PrerenderedFormatsControls, the formats controls for a prerendered page (static defaults in the HTML, the address's values once hydrated, through TanStack'sClientOnly), andFormatsControls'interactiveoption behind it.checkedLocalesin@joeblew999/remy-ui/checks: every locale, or the subset inCHECK_LOCALES; every per-language check iterates it, so the sharedproject:test:quicktier can run the checks on a few representative languages.
Changed
statusCardChecksjumps the refresh interval with Playwright's clock instead of waiting it out (about 10 s to under 1 s).
0.9.1 - 2026-09-25
Fixed
DemoPagenotices input typed before hydration (a prerendered page fires noonInputfor it), so the leave warning still guards it.demoChecks,navigationBlockingChecksandcodeSplittingChecksact only once React has hydrated the element, instead of racing hydration on prerendered pages.
Added
hydrated(locator)in@joeblew999/remy-ui/checks: resolves once React has hydrated an element.
0.9.0 - 2026-09-25
The move to TanStack Start, Router and Query, with the TanStack showcase (plan). Replaces React Router: consumers move their routes to TanStack file routes (see remy-auth and remy-auth-app).
Added
@joeblew999/remy-ui/tanstack:localizedWorker(service, start), the Worker entry for a server-rendered Start app (withObservabilityaround Paraglide's middleware around Start's handler, which gets the original request so its Cloudflarecfproperties reach server functions; un-localized entry URLs answer 302 withVary; HTML isno-store);entryRedirect;localeRewrite, the routerrewrite(Paraglide'sdeLocalizeUrl/localizeUrl);pageHead({ path, title, description })for a route'shead(): title, description, self-canonical and reciprocal hreflang;suggestedLocale(request)andsuggestedLocaleInBrowser(page)for the language hint.DemoPagetakes optionalonReserve(called once the form's own validation passes; its field errors or confirmation message are shown) andonDirtyChange(unsaved input), and exports theReservationandReservationResulttypes. Without them it behaves as before.- Export patterns
./showcase/*(src/showcase/*.tsx) and./showcase/*.checks(src/showcase/*.checks.js) for the TanStack showcase's components and checks. worker: the inner handler receives the generated request ID asX-Request-ID(a client's own value is replaced); new exportslogContext,writeLog,outcome,levelandrequestIdHeader, so the log contract has one definition.- Showcase modules:
showcase/search-params(validateSearch,searchDefaults,calendarsFor,FormatsControls),showcase/navigation-blocking(useLeaveGuard) andshowcase/time-zone(TimeZonePage,canonicalTimeZone,timeZoneName,timeZonePath). - Showcase checks:
search-params,preload,navigation-blocking,server-functions,deferred-place,status-card,problem(localized 404 for an unknown sub-resource, error page without leaks and retry, read-only server routes with caching and 405),code-splittingandbuild-boundaries(serverOnlyMarkers,devtoolsMarkers). - Catalog keys for the search-param controls, the leave-page warning, the live status card, the retry button and the time zone page.
Changed
pages: in-app links are TanStackLinks to de-localized paths withpreload="intent"; the router's rewrite adds the locale. Language changes stay plain anchors (full navigations).- Peer dependency
react-routerreplaced by@tanstack/react-router(optional). HomePagetakeschildren, shown inside the page under its links (the live status card).LanguageHintputs its two actions under the text instead of shadcn's absolutely placedAlertAction, which overlapped the text at phone width.DemoPageshows the localized "Please try again." whenonReserverejects, instead of an unhandled rejection.performanceChecksno longer warms a page before Lighthouse measures it. The warm-up hid a first-layout stall of seconds that every visitor paid in a fresh Chrome renderer on macOS; the cause was font-family names that never load (Inter,ui-monospace,SFMono-Regular, ...), fixed by the newfonts.cssexport (generic families only:system-ui, sans-serifandmonospace), which each app imports afterstyles.css.publicPageChecksfails when any element on any page uses a non-generic font family or the page declares a web font.
Removed
@joeblew999/remy-ui/react-router(languageMiddleware,redirectToLocalized,pageMeta,requireLocale,suggestedLocale); routes no longer carry a:localesegment.
0.8.0 - 2026-09-24
Added
@joeblew999/remy-ui/worker:withObservability(service, handler)wraps any Worker's fetch:X-Request-IDon every response, one structured log line per request following the shared contract (schemaVersion,service,environment,releasefrom the version metadata binding,event,level,requestId, route template,method,status,outcome,reasonCodeon failures, never URLs or headers), and/healthzfor liveness.observabilityChecks({ service, paths })in the shared checks.
0.7.0 - 2026-09-24
Added
@joeblew999/remy-ui/pages:Shell,HomePage,DemoPage,FormatsPage(with slots for an app's extra rows and sections),GroupandRow: the pages the shared checks test, so both apps render the same markup instead of keeping copies.@joeblew999/remy-ui/paths:publicPathsas plain JavaScript for route configs and specs.requireLocalein@joeblew999/remy-ui/react-router.
0.6.0 - 2026-09-24
Changed
playwrightConfig()splits checks into two levels:ours(the app's own checks, fast) andgoogleplusgoogle-cwv(Lighthouse audits and Core Web Vitals, slow). The sharedproject:testruns level 1;project:test:googleruns level 2;project:test:remoteruns both against a deployment.
0.5.0 - 2026-09-24
Added
performanceChecksin@joeblew999/remy-ui/checks: Core Web Vitals through Google'slighthousepackage (optional peer) over Playwright's Chrome, gated on Google's "good" thresholds (LCP 2.5 s, CLS 0.1, TBT 200 ms) and a Performance score of at least 0.9, with the HTML report attached. The Chrome DevTools CLI excludes that category by design.playwrightConfig()runstests/performance.spec.tsalone after every other file, so parallel browsers cannot inflate the timings, and Lighthouse measures a page the check has already warmed, since a fresh browser's cold font scan is not a page cost.
Changed
- Every component in
src/componentsis generated by the pinned shadcn CLI from the officialbase-novaregistry (button, card, input, label, badge, alert, separator, field);mise run ui:componentsregenerates them and the gate fails on any drift. Exported as@joeblew999/remy-ui/components/*. LanguageSwitcherandLanguageHintare built from those components (Alert,Button) and Tailwind utilities; the package ships no component CSS of its own.
0.4.0 - 2026-09-24
Added
@joeblew999/remy-ui/playwright:playwrightConfig(), the shared Playwright configuration (local target on Cloudflare's local host atPREVIEW_PORT, remote target fromTEST_BASE_URL, per-target HTML reports), so a project's config is one call.
0.3.0 - 2026-09-24
Added
@joeblew999/remy-ui/checks: shared Playwright checks (publicPageChecks,entryChecks,demoChecks,formatsChecks,lighthouseChecks, pluscollectErrors,endonym,direction,localizedPath) so every app built on the package runs the same Google-facing checks;@playwright/testis an optional peer.@joeblew999/remy-ui/samples: the fixed sample values the formats and demo pages render and the checks expect.
0.2.0 - 2026-09-24
Added
- Paraglide strategies
url,cookie,preferredLanguage,baseLocalewith URL patterns that keep every locale prefixed;@joeblew999/remy-ui/localere-exports the runtime'sgetLocale,setLocale,localizeHref,localizeUrl,deLocalizeHref,cookieNameand textdirection. @joeblew999/remy-ui/language:LanguageSwitcherandLanguageHinton Paraglide's localized hrefs andsetLocale, with their styles instyles.css.@joeblew999/remy-ui/react-router:languageMiddleware(Paraglide's middleware as root middleware),suggestedLocale,redirectToLocalizedandpageMeta;react-routeris an optional peer.@joeblew999/remy-ui/seo:alternatesbuilt from Paraglide's URL patterns.@joeblew999/remy-ui/client:useSuggestedLocaleandDeviceTimefor prerendered apps.@joeblew999/remy-ui/cloudflare:placeFromCloudflare.@joeblew999/remy-ui/runtime: the generated runtime as plain JavaScript for build configs.
Changed
- The compiler options live in
packages/ui/paraglide.mjs, shared by the Vite plugin andmise run ui:generate. The remembered-choice cookie is Paraglide'sPARAGLIDE_LOCALE.
0.1.0 - 2026-09-24
Added
@joeblew999/remy-ui/button: the shadcnbase-nova/ Base UI button with Remy's variants.@joeblew999/remy-ui/styles.css: Remy's stone/orange theme tokens for light and dark mode.@joeblew999/remy-ui/messages: compiled Paraglide catalogs for English, Spanish and Arabic, includingnumber,datetime,relativetimeandpluralformatters. The Arabic catalog is agent-authored and not yet reviewed.@joeblew999/remy-ui/locale: the locale list,isLocale,baseLocale, readingdirectionand endonymlocaleNamefrom Intl.@joeblew999/remy-ui/locale-info: a locale's calendars, numbering system, hour cycle and week conventions, plusweekdayName.