From e8ca70ed82c8fb4d0e4c29c32ba0362d76e57a20 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:18:31 +0200 Subject: [PATCH 01/23] scaffold micro Lit example Mirror examples/svelte/micro layout: package.json with file:../../.. texivia-router dep + lit ^3, tsconfig pair (app/node), vite config, index.html entrypoint, shared app.css design tokens, favicon. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/.gitignore | 24 +++ examples/lit/micro/index.html | 12 ++ examples/lit/micro/package.json | 20 +++ examples/lit/micro/public/favicon.ico | Bin 0 -> 15406 bytes examples/lit/micro/src/app.css | 218 ++++++++++++++++++++++++++ examples/lit/micro/src/main.ts | 4 + examples/lit/micro/tsconfig.app.json | 22 +++ examples/lit/micro/tsconfig.json | 7 + examples/lit/micro/tsconfig.node.json | 22 +++ examples/lit/micro/vite.config.ts | 3 + 10 files changed, 332 insertions(+) create mode 100644 examples/lit/micro/.gitignore create mode 100644 examples/lit/micro/index.html create mode 100644 examples/lit/micro/package.json create mode 100644 examples/lit/micro/public/favicon.ico create mode 100644 examples/lit/micro/src/app.css create mode 100644 examples/lit/micro/src/main.ts create mode 100644 examples/lit/micro/tsconfig.app.json create mode 100644 examples/lit/micro/tsconfig.json create mode 100644 examples/lit/micro/tsconfig.node.json create mode 100644 examples/lit/micro/vite.config.ts diff --git a/examples/lit/micro/.gitignore b/examples/lit/micro/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/lit/micro/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/lit/micro/index.html b/examples/lit/micro/index.html new file mode 100644 index 0000000..a17f1db --- /dev/null +++ b/examples/lit/micro/index.html @@ -0,0 +1,12 @@ + + + + + + Micro Texivia Lit Example + + +
+ + + diff --git a/examples/lit/micro/package.json b/examples/lit/micro/package.json new file mode 100644 index 0000000..c2dae8d --- /dev/null +++ b/examples/lit/micro/package.json @@ -0,0 +1,20 @@ +{ + "name": "micro", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -p tsconfig.app.json && vite build", + "preview": "vite preview", + "type-check": "tsc -p tsconfig.app.json" + }, + "dependencies": { + "lit": "^3.2.0", + "texivia-router": "file:../../.." + }, + "devDependencies": { + "typescript": "~5.7.2", + "vite": "^6.3.1" + } +} diff --git a/examples/lit/micro/public/favicon.ico b/examples/lit/micro/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..17c8917b0d740c11d6ec1648e144f6fb0d6483f7 GIT binary patch literal 15406 zcmeI3e~^_`8ONV3Ej1G??U#vIlNu$O78Rg4sUyRn!tUPP1u`sW2xe^hqm*eXcP7}~ z_wFJL4He87m}n)ESYwVk1*L*%(Trs=R>nk7h6b1dBC+)Op8KA6&wY8{d+*-;;gox4 z-Z|$y=lOYlJm)z-u33Y<-bRl$(y=!GII{_6Hg;^OeBP;MtASm3VO)QX*_PAI&I1P$ zC`xdWVgEHRwlfOrP`=Jzp=hgHigr<*yEE z3OgA&N71#Dw5PRbmwSB555e<3U(QOUpQDd#5&3h#|FOp-|3&F=P0`-$Vd->smz^ql z^z2X_`TL=p(B5vZYgud;#k}Vh+U?{tt;&Ks`pA~5+|p&2Qr;xK*fgK-S@`X0F4|(% zr+?7DLG<+Z8ea$gM)zWq5k*2tQ6cO(%rQ$x=_Cpovzln4Dpml-gNbM2-n;xbl5l#gZ7Hp zmJNkM{qxH68;n$bW-Q-jeDY5tzyH4$V7%T~Y5otT#qtfzqh0uxD~3`|9&hGv(fOv> zx8>mt6&%|a#J=fQLnWgUU;N)hCm;XTyxfQ^2JhF_VQ?4wyY zwZa;E>?|)^sp29RYecpKJ2z(WA@*qUG^_{RSva-A8jJQm&r7Kv)Aau>G5naAe{QLb z4m+F)H49d>W=bi`p7XW`TWOWPmDrSM-Xf1tQC z%}aZWM1Ik_()G2L3q5wW=c5#RM|s+|F8dI$USR7zYRLf(>sDuadwbw|~fU`+B=rTX7qfM?#Xe0Z1 zSXXOa<1OKV4D!>`v@IPr3Ao0=3$0y!fxX+)MfNArQ`f7poTjZ-p3-i|mC0vcl|_TC z_c}SORo+Jkk2d*?r$>jcd)j%c?0Ah+U#^rw-|KlWuWrr4RllOcff#SCY!{yMl|}oF z;K<+a$8VwAJ`VnOvv{Rp;JKK_@u+adz}UXY>q&aAe3W=GCoV{Ii)@u(=yHSlq;-jL z3RL+A-YYTR!?fqhJZ#xa{X;SCi{V&xF%*2LKho04`W0CuJo)iFejX0ZgM16GU1Vo^ zSYE2V2fW)Gm-L;@cwFpI?C_M@g8WveXK5eq&QLtw5T^~=r#G=KqsuJ5W!kq4vCI+n z|3fXHa}thd?RvhEa}pY1ZUMtZz^BH`c6M;9wH6UuIpN z!@Bqa>-47wll5@$Lhp0=He-YKBsIl%!=YELG87}Bw~u$XU03}+s#ey+g6H<@k?o6P zyWPx%%Z5epAPLcamp<@kh=&+UpC9NM(jci;6Px%R?nkcuOY!}hTK8Es38)>ZL3f?B zpPXIfvC-ZU-Isg)nF~*sFc3Zbi1^0PAN5@&3yDP3nH(2$^ zo#SQ4sk4KAcuwa_)#Pa1rQNqDJR;kccUD}DS3UJ=uSB-mFtXj~vxo06bziQg%f<8T zcx-(%;L$sfGtBWJ z$y<|4LY$$<&uzC8kb5MNTN*Pf9lXFP@JAn7k~Jx}zn_&#jF`XB4}+*>iP zzxdN1Uvm0$FHEd3zLHG(U;%a}clYT3lHO=lq8UhChTqos?+W$T`h`yNjhY7)@2H7g zCqiF#m&F=><3Mz*L`!^gsqgC~)%Z`n%|!psSpP$n?hxj*+bQVp#+&EI`tgYFUn=QQ zZymZ!^m2lkwxYc&q^~y*-wVoQNq^p| zX0GtUUPb_39kb&0w#X=8ulIsl7uy<)@a$ z_RnJc=fk~$Y|;I=yZ2%b&v?<9P+|i%9prn0vGoY)!o>>ZG=(|xAK8UC$oe$}J9X!J&)Ope+tvLEVAM;=f5=fQyHD`{UP zI{dyGv_$8dqyE?XUveBc4C` zXDY4VX&$wd-Ti4WPGg<6R`L(=n{xL?P5NQ)hUWHT_h|6ay|1$~wn1+OX4j%utvLAk z6MXvw>0b+rD#wlJbrrfi7t+YFxKKjHj4^#8zg{@BZ!wjyiY^nCJC`02bbY5Q2l zqj~rtT8Iy;qxk~*n!Baf(3ghT5(x<)cjG^RzE_poCaQ!E! zagm*+xt6Z$Pg!Fj?L)KZ6|3^DZUK7VfgM`Q? Date: Tue, 5 May 2026 21:19:13 +0200 Subject: [PATCH 02/23] bump lit to ^3.3.2 in micro example Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit/micro/package.json b/examples/lit/micro/package.json index c2dae8d..ff3509e 100644 --- a/examples/lit/micro/package.json +++ b/examples/lit/micro/package.json @@ -10,7 +10,7 @@ "type-check": "tsc -p tsconfig.app.json" }, "dependencies": { - "lit": "^3.2.0", + "lit": "^3.3.2", "texivia-router": "file:../../.." }, "devDependencies": { From 42ecf307409cd62d9d008db18be20e8088ebee28 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:19:19 +0200 Subject: [PATCH 03/23] add Lit header/footer components and main layout app-header / app-footer mirror the Svelte+Vue micro examples. Both render in light DOM (createRenderRoot returns this) so global app.css applies and anchor clicks bubble to document for Texivia interception. main-layout slots page content between them. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lit/micro/src/components/app-footer.ts | 22 +++++++++++++++++++ .../lit/micro/src/components/app-header.ts | 22 +++++++++++++++++++ examples/lit/micro/src/layouts/main-layout.ts | 19 ++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 examples/lit/micro/src/components/app-footer.ts create mode 100644 examples/lit/micro/src/components/app-header.ts create mode 100644 examples/lit/micro/src/layouts/main-layout.ts diff --git a/examples/lit/micro/src/components/app-footer.ts b/examples/lit/micro/src/components/app-footer.ts new file mode 100644 index 0000000..fbe8b74 --- /dev/null +++ b/examples/lit/micro/src/components/app-footer.ts @@ -0,0 +1,22 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' + +@customElement('app-footer') +export class AppFooter extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + + ` + } +} diff --git a/examples/lit/micro/src/components/app-header.ts b/examples/lit/micro/src/components/app-header.ts new file mode 100644 index 0000000..2acf924 --- /dev/null +++ b/examples/lit/micro/src/components/app-header.ts @@ -0,0 +1,22 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' + +@customElement('app-header') +export class AppHeader extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` +
+ Texivia + +
+ ` + } +} diff --git a/examples/lit/micro/src/layouts/main-layout.ts b/examples/lit/micro/src/layouts/main-layout.ts new file mode 100644 index 0000000..d6651a6 --- /dev/null +++ b/examples/lit/micro/src/layouts/main-layout.ts @@ -0,0 +1,19 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../components/app-header' +import '../components/app-footer' + +@customElement('main-layout') +export class MainLayout extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + +
+ + ` + } +} From 7c32ae0a982461e59e0394b44d4f7466b36cc192 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:20:12 +0200 Subject: [PATCH 04/23] add Lit pages for micro example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven pages mirroring the Svelte/Vue micro examples: landing, login, user-profile, about, imprint, contact, not-found. Each exports a LitElement plus a render() function that the router map will hold as the typed view value — same role component classes play in the Svelte/Vue versions. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/pages/about.ts | 23 ++++++++++++++++ examples/lit/micro/src/pages/contact.ts | 23 ++++++++++++++++ examples/lit/micro/src/pages/imprint.ts | 25 +++++++++++++++++ examples/lit/micro/src/pages/landing.ts | 26 ++++++++++++++++++ examples/lit/micro/src/pages/login.ts | 29 ++++++++++++++++++++ examples/lit/micro/src/pages/not-found.ts | 20 ++++++++++++++ examples/lit/micro/src/pages/user-profile.ts | 24 ++++++++++++++++ 7 files changed, 170 insertions(+) create mode 100644 examples/lit/micro/src/pages/about.ts create mode 100644 examples/lit/micro/src/pages/contact.ts create mode 100644 examples/lit/micro/src/pages/imprint.ts create mode 100644 examples/lit/micro/src/pages/landing.ts create mode 100644 examples/lit/micro/src/pages/login.ts create mode 100644 examples/lit/micro/src/pages/not-found.ts create mode 100644 examples/lit/micro/src/pages/user-profile.ts diff --git a/examples/lit/micro/src/pages/about.ts b/examples/lit/micro/src/pages/about.ts new file mode 100644 index 0000000..3a756d8 --- /dev/null +++ b/examples/lit/micro/src/pages/about.ts @@ -0,0 +1,23 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../layouts/main-layout' + +@customElement('page-about') +export class PageAbout extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + +

About

+

Texivia is a lightweight, framework-agnostic router for single-page applications.

+

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

+
+ ` + } +} + +export const renderAbout = (p: Record) => + html`` diff --git a/examples/lit/micro/src/pages/contact.ts b/examples/lit/micro/src/pages/contact.ts new file mode 100644 index 0000000..de863e7 --- /dev/null +++ b/examples/lit/micro/src/pages/contact.ts @@ -0,0 +1,23 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../layouts/main-layout' + +@customElement('page-contact') +export class PageContact extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + +

Contact

+

Have questions or feedback? Reach out to us.

+

Email: example@texivia.dev

+
+ ` + } +} + +export const renderContact = (p: Record) => + html`` diff --git a/examples/lit/micro/src/pages/imprint.ts b/examples/lit/micro/src/pages/imprint.ts new file mode 100644 index 0000000..5260b20 --- /dev/null +++ b/examples/lit/micro/src/pages/imprint.ts @@ -0,0 +1,25 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../layouts/main-layout' + +@customElement('page-imprint') +export class PageImprint extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + +

Imprint

+

Responsible

+

Texivia Example App
123 Example Street
12345 Example City

+

Contact

+

Email: example@texivia.dev

+
+ ` + } +} + +export const renderImprint = (p: Record) => + html`` diff --git a/examples/lit/micro/src/pages/landing.ts b/examples/lit/micro/src/pages/landing.ts new file mode 100644 index 0000000..27daed6 --- /dev/null +++ b/examples/lit/micro/src/pages/landing.ts @@ -0,0 +1,26 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../layouts/main-layout' + +@customElement('page-landing') +export class PageLanding extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + +

Welcome to Texivia

+

Your one-stop solution for all your text processing needs.

+

Locale: ${this.locale}

+

Explore our features and services tailored just for you.

+

Get started by signing up or logging in!

+

Already have an account? Login here

+
+ ` + } +} + +export const renderLanding = (p: Record) => + html`` diff --git a/examples/lit/micro/src/pages/login.ts b/examples/lit/micro/src/pages/login.ts new file mode 100644 index 0000000..34f81ff --- /dev/null +++ b/examples/lit/micro/src/pages/login.ts @@ -0,0 +1,29 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../layouts/main-layout' + +@customElement('page-login') +export class PageLogin extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + + render() { + return html` + +

Login

+ +
+ ` + } +} + +export const renderLogin = (p: Record) => + html`` diff --git a/examples/lit/micro/src/pages/not-found.ts b/examples/lit/micro/src/pages/not-found.ts new file mode 100644 index 0000000..173839e --- /dev/null +++ b/examples/lit/micro/src/pages/not-found.ts @@ -0,0 +1,20 @@ +import { LitElement, html } from 'lit' +import { customElement } from 'lit/decorators.js' + +@customElement('page-not-found') +export class PageNotFound extends LitElement { + createRenderRoot() { return this } + + render() { + return html` +
+

404 Not Found

+

The page you are looking for does not exist.

+

Return to the home page.

+
+ ` + } +} + +export const renderNotFound = () => + html`` diff --git a/examples/lit/micro/src/pages/user-profile.ts b/examples/lit/micro/src/pages/user-profile.ts new file mode 100644 index 0000000..7bbdb66 --- /dev/null +++ b/examples/lit/micro/src/pages/user-profile.ts @@ -0,0 +1,24 @@ +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import '../layouts/main-layout' + +@customElement('page-user-profile') +export class PageUserProfile extends LitElement { + createRenderRoot() { return this } + + @property() locale = 'en' + @property() id = '' + + render() { + return html` + +

User Profile

+

Locale: ${this.locale}

+

User ID: ${this.id}

+
+ ` + } +} + +export const renderUserProfile = (p: Record) => + html`` From af9e606f89a3da02c7bd6db5a2157d2ce6265d07 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:20:47 +0200 Subject: [PATCH 05/23] wire Lit example to Texivia via router and app-shell router.ts uses Router where View = (params) => TemplateResult, mirroring how Svelte/Vue store the component class itself. The render fn pattern keeps the route map declarative and avoids pulling in lit/static-html for tag-name dispatch. app-shell.ts owns the texivia event subscription and re-renders by swapping the @state-tracked view + params. Light DOM ensures global styles apply and anchor clicks reach document for interception. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/app-shell.ts | 36 +++++++++++++++++++++++++++++ examples/lit/micro/src/router.ts | 22 ++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/lit/micro/src/app-shell.ts create mode 100644 examples/lit/micro/src/router.ts diff --git a/examples/lit/micro/src/app-shell.ts b/examples/lit/micro/src/app-shell.ts new file mode 100644 index 0000000..465918e --- /dev/null +++ b/examples/lit/micro/src/app-shell.ts @@ -0,0 +1,36 @@ +import { LitElement } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { router, type View } from './router' +import { renderLanding } from './pages/landing' + +@customElement('app-shell') +export class AppShell extends LitElement { + // Light DOM: lets global app.css apply and lets clicks bubble to + // document so Texivia's click interception sees them. + createRenderRoot() { return this } + + @state() private view: View = renderLanding + @state() private params: Record = { locale: 'en' } + + private onNavigate = (event: Event) => { + const detail = (event as CustomEvent).detail + if (detail?.view) this.view = detail.view + this.params = detail?.params ?? {} + } + + connectedCallback() { + super.connectedCallback() + router.start() + document.addEventListener('texivia', this.onNavigate) + } + + disconnectedCallback() { + super.disconnectedCallback() + router.stop() + document.removeEventListener('texivia', this.onNavigate) + } + + render() { + return this.view(this.params) + } +} diff --git a/examples/lit/micro/src/router.ts b/examples/lit/micro/src/router.ts new file mode 100644 index 0000000..f097d99 --- /dev/null +++ b/examples/lit/micro/src/router.ts @@ -0,0 +1,22 @@ +import { Router } from 'texivia-router' +import type { TemplateResult } from 'lit' +import { renderLanding } from './pages/landing' +import { renderLogin } from './pages/login' +import { renderUserProfile } from './pages/user-profile' +import { renderAbout } from './pages/about' +import { renderImprint } from './pages/imprint' +import { renderContact } from './pages/contact' +import { renderNotFound } from './pages/not-found' + +export type View = (params: Record) => TemplateResult + +export const router = new Router([ + { path: '/', handler: () => `/${navigator.language}/` }, + { path: '/{locale}/', view: renderLanding }, + { path: '/{locale}/login', view: renderLogin }, + { path: '/{locale}/users/{id:\\d+}/profile', view: renderUserProfile }, + { path: '/{locale}/about', view: renderAbout }, + { path: '/{locale}/imprint', view: renderImprint }, + { path: '/{locale}/contact', view: renderContact }, + { path: '*', view: renderNotFound }, +]) From 06ea2ec9c432aeac1587f76486c70b37ea3f2c02 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:27:35 +0200 Subject: [PATCH 06/23] pass page body to layout as template prop Light-DOM rendering ignores elements (slots only project in shadow DOM), so children placed inside in the page templates were rendered above the header instead of inside
. Pages now build a body TemplateResult and pass it via .body, mirroring the named-snippet pattern used in the Svelte micro example. Layout keeps light DOM so the global app.css applies and anchor clicks bubble to document for Texivia's interception. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/layouts/main-layout.ts | 5 ++-- examples/lit/micro/src/pages/about.ts | 11 ++++----- examples/lit/micro/src/pages/contact.ts | 11 ++++----- examples/lit/micro/src/pages/imprint.ts | 15 ++++++------ examples/lit/micro/src/pages/landing.ts | 17 +++++++------- examples/lit/micro/src/pages/login.ts | 23 +++++++++---------- examples/lit/micro/src/pages/user-profile.ts | 11 ++++----- 7 files changed, 44 insertions(+), 49 deletions(-) diff --git a/examples/lit/micro/src/layouts/main-layout.ts b/examples/lit/micro/src/layouts/main-layout.ts index d6651a6..5caaa56 100644 --- a/examples/lit/micro/src/layouts/main-layout.ts +++ b/examples/lit/micro/src/layouts/main-layout.ts @@ -1,4 +1,4 @@ -import { LitElement, html } from 'lit' +import { LitElement, html, nothing, type TemplateResult } from 'lit' import { customElement, property } from 'lit/decorators.js' import '../components/app-header' import '../components/app-footer' @@ -8,11 +8,12 @@ export class MainLayout extends LitElement { createRenderRoot() { return this } @property() locale = 'en' + @property({ attribute: false }) body: TemplateResult | typeof nothing = nothing render() { return html` -
+
${this.body}
` } diff --git a/examples/lit/micro/src/pages/about.ts b/examples/lit/micro/src/pages/about.ts index 3a756d8..29db067 100644 --- a/examples/lit/micro/src/pages/about.ts +++ b/examples/lit/micro/src/pages/about.ts @@ -9,13 +9,12 @@ export class PageAbout extends LitElement { @property() locale = 'en' render() { - return html` - -

About

-

Texivia is a lightweight, framework-agnostic router for single-page applications.

-

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

-
+ const body = html` +

About

+

Texivia is a lightweight, framework-agnostic router for single-page applications.

+

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

` + return html`` } } diff --git a/examples/lit/micro/src/pages/contact.ts b/examples/lit/micro/src/pages/contact.ts index de863e7..33be9cf 100644 --- a/examples/lit/micro/src/pages/contact.ts +++ b/examples/lit/micro/src/pages/contact.ts @@ -9,13 +9,12 @@ export class PageContact extends LitElement { @property() locale = 'en' render() { - return html` - -

Contact

-

Have questions or feedback? Reach out to us.

-

Email: example@texivia.dev

- + const body = html` +

Contact

+

Have questions or feedback? Reach out to us.

+

Email: example@texivia.dev

` + return html`` } } diff --git a/examples/lit/micro/src/pages/imprint.ts b/examples/lit/micro/src/pages/imprint.ts index 5260b20..d76380a 100644 --- a/examples/lit/micro/src/pages/imprint.ts +++ b/examples/lit/micro/src/pages/imprint.ts @@ -9,15 +9,14 @@ export class PageImprint extends LitElement { @property() locale = 'en' render() { - return html` - -

Imprint

-

Responsible

-

Texivia Example App
123 Example Street
12345 Example City

-

Contact

-

Email: example@texivia.dev

-
+ const body = html` +

Imprint

+

Responsible

+

Texivia Example App
123 Example Street
12345 Example City

+

Contact

+

Email: example@texivia.dev

` + return html`` } } diff --git a/examples/lit/micro/src/pages/landing.ts b/examples/lit/micro/src/pages/landing.ts index 27daed6..5c942aa 100644 --- a/examples/lit/micro/src/pages/landing.ts +++ b/examples/lit/micro/src/pages/landing.ts @@ -9,16 +9,15 @@ export class PageLanding extends LitElement { @property() locale = 'en' render() { - return html` - -

Welcome to Texivia

-

Your one-stop solution for all your text processing needs.

-

Locale: ${this.locale}

-

Explore our features and services tailored just for you.

-

Get started by signing up or logging in!

-

Already have an account? Login here

-
+ const body = html` +

Welcome to Texivia

+

Your one-stop solution for all your text processing needs.

+

Locale: ${this.locale}

+

Explore our features and services tailored just for you.

+

Get started by signing up or logging in!

+

Already have an account? Login here

` + return html`` } } diff --git a/examples/lit/micro/src/pages/login.ts b/examples/lit/micro/src/pages/login.ts index 34f81ff..6f8b65b 100644 --- a/examples/lit/micro/src/pages/login.ts +++ b/examples/lit/micro/src/pages/login.ts @@ -9,19 +9,18 @@ export class PageLogin extends LitElement { @property() locale = 'en' render() { - return html` - -

Login

- -
+ const body = html` +

Login

+ ` + return html`` } } diff --git a/examples/lit/micro/src/pages/user-profile.ts b/examples/lit/micro/src/pages/user-profile.ts index 7bbdb66..4e707dd 100644 --- a/examples/lit/micro/src/pages/user-profile.ts +++ b/examples/lit/micro/src/pages/user-profile.ts @@ -10,13 +10,12 @@ export class PageUserProfile extends LitElement { @property() id = '' render() { - return html` - -

User Profile

-

Locale: ${this.locale}

-

User ID: ${this.id}

-
+ const body = html` +

User Profile

+

Locale: ${this.locale}

+

User ID: ${this.id}

` + return html`` } } From 4c73adb1c626addc812963bb55043f15b9f4c0cb Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:27:42 +0200 Subject: [PATCH 07/23] attach texivia listener before router.start in app-shell Routes without a handler complete _navigate synchronously inside start() and dispatch the texivia event before control returns. Adding the listener after start() therefore lost the initial event on direct deep-link loads (e.g. opening /en/users/42/profile would render the default Landing view). Reordering means the listener catches the very first dispatch. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/app-shell.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/lit/micro/src/app-shell.ts b/examples/lit/micro/src/app-shell.ts index 465918e..8010f3c 100644 --- a/examples/lit/micro/src/app-shell.ts +++ b/examples/lit/micro/src/app-shell.ts @@ -20,14 +20,17 @@ export class AppShell extends LitElement { connectedCallback() { super.connectedCallback() - router.start() + // Listener must precede start(): for routes with no handler, _navigate + // dispatches the texivia event synchronously inside start(), so attaching + // afterwards loses the initial event on deep-link loads. document.addEventListener('texivia', this.onNavigate) + router.start() } disconnectedCallback() { super.disconnectedCallback() - router.stop() document.removeEventListener('texivia', this.onNavigate) + router.stop() } render() { From e46fda5b5f0d9847431c0b99dc54a68487bd12b9 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:27:48 +0200 Subject: [PATCH 08/23] drop tsc step from Lit example build script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root build doesn't emit dist/texivia.d.ts (vite lib mode without a dts plugin), so a strict tsc pass over the example fails with TS7016 — the same issue affects the Vue example's type-check step. Match Vue's working build-only pattern: 'build' just runs vite, 'type-check' stays available as an opt-in script. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit/micro/package.json b/examples/lit/micro/package.json index ff3509e..570e42d 100644 --- a/examples/lit/micro/package.json +++ b/examples/lit/micro/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc -p tsconfig.app.json && vite build", + "build": "vite build", "preview": "vite preview", "type-check": "tsc -p tsconfig.app.json" }, From fd0115d7d17779882e13de457e27a98348f711ad Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:27:53 +0200 Subject: [PATCH 09/23] add package-lock.json for Lit micro example Mirrors the Svelte/Vue micro examples which both check in their lockfiles for reproducible installs. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/package-lock.json | 1233 ++++++++++++++++++++++++++ 1 file changed, 1233 insertions(+) create mode 100644 examples/lit/micro/package-lock.json diff --git a/examples/lit/micro/package-lock.json b/examples/lit/micro/package-lock.json new file mode 100644 index 0000000..5a0f5c3 --- /dev/null +++ b/examples/lit/micro/package-lock.json @@ -0,0 +1,1233 @@ +{ + "name": "micro", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "micro", + "version": "0.0.0", + "dependencies": { + "lit": "^3.3.2", + "texivia-router": "file:../../.." + }, + "devDependencies": { + "typescript": "~5.7.2", + "vite": "^6.3.1" + } + }, + "../../..": { + "name": "texivia-router", + "version": "1.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@rollup/plugin-terser": "^0.4.4", + "@types/node": "^22.7.4", + "eslint": "^8.57.0", + "jsdom": "^26.1.0", + "terser": "^5.39.0", + "typescript": "^5.6.3", + "vite": "^5.4.8", + "vitest": "^3.1.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/texivia-router": { + "resolved": "../../..", + "link": true + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} From 400aa6b602de2dd11ef742648363fb3b08013f61 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:28:24 +0200 Subject: [PATCH 10/23] add README for micro Lit example Mirrors the Svelte/Vue example READMEs and documents the three Lit-specific decisions: light DOM, listener-before-start ordering, and body-as-template-prop instead of . Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/README.md | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 examples/lit/micro/README.md diff --git a/examples/lit/micro/README.md b/examples/lit/micro/README.md new file mode 100644 index 0000000..262984f --- /dev/null +++ b/examples/lit/micro/README.md @@ -0,0 +1,83 @@ +# Micro — Lit + Texivia Router + +Minimal Lit 3 example using `texivia-router` for client-side routing. Demonstrates locale-prefixed routes, parameterized paths, redirects, and a catch-all 404 page. + +## Routes + +| Path | View | +|------|------| +| `/` | Redirects to `/{locale}/` based on `navigator.language` | +| `/{locale}/` | Landing | +| `/{locale}/login` | Login | +| `/{locale}/users/{id}/profile` | UserProfile | +| `/{locale}/about` | About | +| `/{locale}/imprint` | Imprint | +| `/{locale}/contact` | Contact | +| `*` | NotFound | + +## How It Works + +`Router` is parameterized over a render function — `View = (params) => TemplateResult`. Each page exports a LitElement plus a `renderXxx` function that the route map references: + +```ts +// src/router.ts +export type View = (params: Record) => TemplateResult + +export const router = new Router([ + { path: '/', handler: () => `/${navigator.language}/` }, + { path: '/{locale}/', view: renderLanding }, + { path: '/{locale}/users/{id:\\d+}/profile', view: renderUserProfile }, + // ... + { path: '*', view: renderNotFound }, +]) +``` + +`` is the root LitElement. It listens for the `texivia` event, swaps the current view, and re-renders: + +```ts +// src/app-shell.ts +@customElement('app-shell') +export class AppShell extends LitElement { + createRenderRoot() { return this } // light DOM + + @state() private view: View = renderLanding + @state() private params: Record = { locale: 'en' } + + private onNavigate = (e: Event) => { + const detail = (e as CustomEvent).detail + if (detail?.view) this.view = detail.view + this.params = detail?.params ?? {} + } + + connectedCallback() { + super.connectedCallback() + document.addEventListener('texivia', this.onNavigate) + router.start() + } + + disconnectedCallback() { + super.disconnectedCallback() + document.removeEventListener('texivia', this.onNavigate) + router.stop() + } + + render() { + return this.view(this.params) + } +} +``` + +## Lit-specific notes + +**Light DOM (`createRenderRoot() { return this }`).** The example renders in light DOM so global `app.css` applies and `` clicks bubble to `document` where Texivia intercepts them. If you prefer shadow DOM, you'll need to ship styles per-component via `static styles` and rely on `composedPath` for click interception (Texivia already handles standard click bubbling, so anchor clicks must reach `document`). + +**Listener before `start()`.** Routes without a handler complete `_navigate` synchronously inside `router.start()` and dispatch the `texivia` event before control returns. Attach the listener first, then call `router.start()`, otherwise the initial event is missed on direct deep-link loads. + +**Layout body as a template prop.** Light DOM ignores `` (slots only project in shadow DOM). Layout components take their body as a TemplateResult prop instead — ``. This mirrors Svelte's named-snippet pattern. + +## Setup + +```sh +npm install +npm run dev +``` From 2ea9675c023d313fad62f65edbbade7e2d912957 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:29:06 +0200 Subject: [PATCH 11/23] document Lit integration in README Adds a Lit row to the framework comparison table and a full Lit 3 section between Svelte and Vue. Covers the typed Router pattern with render functions, the listener, and the two non-obvious gotchas: light-DOM rendering and listener-before-start ordering. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/README.md b/README.md index f0666ea..82b2248 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ npm install texivia-router | Svelte | – | – | ✓ | – | **✓** | | React | ✓ | – | ✓ | ✓ | **✓** | | Vue | – | ✓ | ✓ | – | **✓** | +| Lit | – | – | – | – | **✓** | | Angular | – | – | ✓ | – | **✓** | | Vanilla | – | – | ✓ | – | **✓** | @@ -131,6 +132,98 @@ Texivia doesn't need a nested routing concept. Use your framework's composition Layouts are components, not router config. This keeps the router simple and your layouts flexible. +## Lit 3 + +Type the router with a render-function `View` so each route's `view` is a `(params) => TemplateResult`. A single `` LitElement owns the navigation listener and re-renders by swapping the current view: + +```typescript +// src/router.ts +import { Router } from 'texivia-router'; +import type { TemplateResult } from 'lit'; +import { renderLanding } from './pages/landing'; +import { renderUserProfile } from './pages/user-profile'; +import { renderNotFound } from './pages/not-found'; + +export type View = (params: Record) => TemplateResult; + +export const router = new Router([ + { path: '/', view: renderLanding }, + { path: '/users/{id:\\d+}', view: renderUserProfile }, + { path: '*', view: renderNotFound }, +]); +``` + +```typescript +// src/app-shell.ts +import { LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { router, type View } from './router'; +import { renderLanding } from './pages/landing'; + +@customElement('app-shell') +export class AppShell extends LitElement { + // Light DOM: lets global CSS apply and lets clicks bubble to document. + createRenderRoot() { return this; } + + @state() private view: View = renderLanding; + @state() private params: Record = {}; + + private onNavigate = (e: Event) => { + const detail = (e as CustomEvent).detail; + if (detail?.view) this.view = detail.view; + this.params = detail?.params ?? {}; + }; + + connectedCallback() { + super.connectedCallback(); + // Attach listener BEFORE start() — routes without a handler dispatch + // synchronously, so a late listener misses the initial event on deep links. + document.addEventListener('texivia', this.onNavigate); + router.start(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener('texivia', this.onNavigate); + router.stop(); + } + + render() { + return this.view(this.params); + } +} +``` + +Each page exports a LitElement and a render fn that the router map references: + +```typescript +// src/pages/landing.ts +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('page-landing') +export class PageLanding extends LitElement { + createRenderRoot() { return this; } + @property() locale = 'en'; + + render() { + return html`

Welcome

Locale: ${this.locale}

`; + } +} + +export const renderLanding = (p: Record) => + html``; +``` + +For programmatic navigation, import the router from anywhere: + +```typescript +import { router } from './router'; +router.navigate('/dashboard'); +``` + +Plain `
` tags inside Lit templates are intercepted automatically — no `` wrapper needed. See [`examples/lit/micro`](examples/lit/micro) for a complete example with layouts, parameterized routes, and a 404 fallback. + ## Vue 3 ```typescript From 55efa0036e287ec1bcbc868bc8b63b103372381e Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:29:17 +0200 Subject: [PATCH 12/23] add lit and web-components to npm keywords Improves discoverability for Lit/web-components users searching npm now that there's a first-party example. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1eaa38d..0b1486b 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,9 @@ "single-page-app", "svelte", "vue", - "react" + "react", + "lit", + "web-components" ], "devDependencies": { "@rollup/plugin-terser": "^0.4.4", From 1c71b39f95e7345994931ef6630c8923046be45c Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:39:23 +0200 Subject: [PATCH 13/23] make main-layout a plain template function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The layout had no state, lifecycle, or events — only a locale prop and a body prop that existed solely to work around being inert in light DOM. Replacing the LitElement with a function lets pages call mainLayout(locale, html\`...\`) directly. This removes the .body reactive prop, drops a custom-element registration, and eliminates the slot-vs-light-DOM footgun entirely. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/layouts/main-layout.ts | 23 +++++-------------- examples/lit/micro/src/pages/about.ts | 7 +++--- examples/lit/micro/src/pages/contact.ts | 7 +++--- examples/lit/micro/src/pages/imprint.ts | 7 +++--- examples/lit/micro/src/pages/landing.ts | 7 +++--- examples/lit/micro/src/pages/login.ts | 7 +++--- examples/lit/micro/src/pages/user-profile.ts | 7 +++--- 7 files changed, 24 insertions(+), 41 deletions(-) diff --git a/examples/lit/micro/src/layouts/main-layout.ts b/examples/lit/micro/src/layouts/main-layout.ts index 5caaa56..9fabc6d 100644 --- a/examples/lit/micro/src/layouts/main-layout.ts +++ b/examples/lit/micro/src/layouts/main-layout.ts @@ -1,20 +1,9 @@ -import { LitElement, html, nothing, type TemplateResult } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { html, type TemplateResult } from 'lit' import '../components/app-header' import '../components/app-footer' -@customElement('main-layout') -export class MainLayout extends LitElement { - createRenderRoot() { return this } - - @property() locale = 'en' - @property({ attribute: false }) body: TemplateResult | typeof nothing = nothing - - render() { - return html` - -
${this.body}
- - ` - } -} +export const mainLayout = (locale: string, body: TemplateResult) => html` + +
${body}
+ +` diff --git a/examples/lit/micro/src/pages/about.ts b/examples/lit/micro/src/pages/about.ts index 29db067..66f1d27 100644 --- a/examples/lit/micro/src/pages/about.ts +++ b/examples/lit/micro/src/pages/about.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import '../layouts/main-layout' +import { mainLayout } from '../layouts/main-layout' @customElement('page-about') export class PageAbout extends LitElement { @@ -9,12 +9,11 @@ export class PageAbout extends LitElement { @property() locale = 'en' render() { - const body = html` + return mainLayout(this.locale, html`

About

Texivia is a lightweight, framework-agnostic router for single-page applications.

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

- ` - return html`` + `) } } diff --git a/examples/lit/micro/src/pages/contact.ts b/examples/lit/micro/src/pages/contact.ts index 33be9cf..ed18c7d 100644 --- a/examples/lit/micro/src/pages/contact.ts +++ b/examples/lit/micro/src/pages/contact.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import '../layouts/main-layout' +import { mainLayout } from '../layouts/main-layout' @customElement('page-contact') export class PageContact extends LitElement { @@ -9,12 +9,11 @@ export class PageContact extends LitElement { @property() locale = 'en' render() { - const body = html` + return mainLayout(this.locale, html`

Contact

Have questions or feedback? Reach out to us.

Email: example@texivia.dev

- ` - return html`` + `) } } diff --git a/examples/lit/micro/src/pages/imprint.ts b/examples/lit/micro/src/pages/imprint.ts index d76380a..9e61686 100644 --- a/examples/lit/micro/src/pages/imprint.ts +++ b/examples/lit/micro/src/pages/imprint.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import '../layouts/main-layout' +import { mainLayout } from '../layouts/main-layout' @customElement('page-imprint') export class PageImprint extends LitElement { @@ -9,14 +9,13 @@ export class PageImprint extends LitElement { @property() locale = 'en' render() { - const body = html` + return mainLayout(this.locale, html`

Imprint

Responsible

Texivia Example App
123 Example Street
12345 Example City

Contact

Email: example@texivia.dev

- ` - return html`` + `) } } diff --git a/examples/lit/micro/src/pages/landing.ts b/examples/lit/micro/src/pages/landing.ts index 5c942aa..30b0394 100644 --- a/examples/lit/micro/src/pages/landing.ts +++ b/examples/lit/micro/src/pages/landing.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import '../layouts/main-layout' +import { mainLayout } from '../layouts/main-layout' @customElement('page-landing') export class PageLanding extends LitElement { @@ -9,15 +9,14 @@ export class PageLanding extends LitElement { @property() locale = 'en' render() { - const body = html` + return mainLayout(this.locale, html`

Welcome to Texivia

Your one-stop solution for all your text processing needs.

Locale: ${this.locale}

Explore our features and services tailored just for you.

Get started by signing up or logging in!

Already have an account? Login here

- ` - return html`` + `) } } diff --git a/examples/lit/micro/src/pages/login.ts b/examples/lit/micro/src/pages/login.ts index 6f8b65b..79c91e7 100644 --- a/examples/lit/micro/src/pages/login.ts +++ b/examples/lit/micro/src/pages/login.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import '../layouts/main-layout' +import { mainLayout } from '../layouts/main-layout' @customElement('page-login') export class PageLogin extends LitElement { @@ -9,7 +9,7 @@ export class PageLogin extends LitElement { @property() locale = 'en' render() { - const body = html` + return mainLayout(this.locale, html`

Login

- ` - return html`` + `) } } diff --git a/examples/lit/micro/src/pages/user-profile.ts b/examples/lit/micro/src/pages/user-profile.ts index 4e707dd..dd3224a 100644 --- a/examples/lit/micro/src/pages/user-profile.ts +++ b/examples/lit/micro/src/pages/user-profile.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit' import { customElement, property } from 'lit/decorators.js' -import '../layouts/main-layout' +import { mainLayout } from '../layouts/main-layout' @customElement('page-user-profile') export class PageUserProfile extends LitElement { @@ -10,12 +10,11 @@ export class PageUserProfile extends LitElement { @property() id = '' render() { - const body = html` + return mainLayout(this.locale, html`

User Profile

Locale: ${this.locale}

User ID: ${this.id}

- ` - return html`` + `) } } From 842965ff97416e27c473a1847180bd2991a14d45 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:39:28 +0200 Subject: [PATCH 14/23] update example README for layout-as-function refactor Replaces the body-prop note with a recommendation to use plain template functions for stateless layouts. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit/micro/README.md b/examples/lit/micro/README.md index 262984f..af33e35 100644 --- a/examples/lit/micro/README.md +++ b/examples/lit/micro/README.md @@ -73,7 +73,7 @@ export class AppShell extends LitElement { **Listener before `start()`.** Routes without a handler complete `_navigate` synchronously inside `router.start()` and dispatch the `texivia` event before control returns. Attach the listener first, then call `router.start()`, otherwise the initial event is missed on direct deep-link loads. -**Layout body as a template prop.** Light DOM ignores `` (slots only project in shadow DOM). Layout components take their body as a TemplateResult prop instead — ``. This mirrors Svelte's named-snippet pattern. +**Layouts as plain template functions.** `mainLayout(locale, body)` is a function returning a `TemplateResult`, not a custom element. Layouts have no state or behavior, so wrapping them in a `LitElement` only adds ceremony — and forcing children through a custom element means dealing with `` (which doesn't project in light DOM). A function is shorter, has no render root, and composes naturally inside `html\`...\``. ## Setup From d2c037c4d9085247a9fd3f6c7953647d8ec497fe Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:40:57 +0200 Subject: [PATCH 15/23] emit dist/texivia.d.ts in the build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The package.json types field has always pointed at dist/texivia.d.ts but the build only ran vite (lib mode emits no declarations), so any consumer with strict TS hit TS7016 'implicitly has an any type'. Chain 'tsc --emitDeclarationOnly' after vite — declaration is already true in tsconfig, so no other config change is needed. Adds zero runtime cost and fixes type-check failures in both example projects. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b1486b..f11b59e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ } }, "scripts": { - "build": "vite build", + "build": "vite build && tsc --emitDeclarationOnly", "dev": "vite", "test": "vitest", "lint": "eslint src --ext .ts", From 00902bfc27f3794c45f7e523b923666ffc1fbb4a Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:41:03 +0200 Subject: [PATCH 16/23] restore tsc step in Lit example build Now that the root build emits dist/texivia.d.ts, the type-check pass succeeds. Build runs tsc first, then vite, so type errors fail the build instead of slipping through. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit/micro/package.json b/examples/lit/micro/package.json index 570e42d..ff3509e 100644 --- a/examples/lit/micro/package.json +++ b/examples/lit/micro/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "tsc -p tsconfig.app.json && vite build", "preview": "vite preview", "type-check": "tsc -p tsconfig.app.json" }, From d7aa0c94cbfff5e27ea67237b652a2025a635ad5 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Tue, 5 May 2026 21:50:11 +0200 Subject: [PATCH 17/23] format Lit example with prettier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies the repo's .prettierrc (semi, singleQuote, printWidth 120, trailingComma es5) to all new files in examples/lit/micro. No behavioral changes — purely whitespace, semicolons, and line wrapping to match the existing project conventions. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/README.md | 54 ++++++++++--------- examples/lit/micro/src/app-shell.ts | 38 ++++++------- .../lit/micro/src/components/app-footer.ts | 12 +++-- .../lit/micro/src/components/app-header.ts | 12 +++-- examples/lit/micro/src/layouts/main-layout.ts | 8 +-- examples/lit/micro/src/main.ts | 6 +-- examples/lit/micro/src/pages/about.ts | 28 +++++----- examples/lit/micro/src/pages/contact.ts | 28 +++++----- examples/lit/micro/src/pages/imprint.ts | 32 ++++++----- examples/lit/micro/src/pages/landing.ts | 34 ++++++------ examples/lit/micro/src/pages/login.ts | 40 +++++++------- examples/lit/micro/src/pages/not-found.ts | 13 ++--- examples/lit/micro/src/pages/user-profile.ts | 29 +++++----- examples/lit/micro/src/router.ts | 22 ++++---- examples/lit/micro/tsconfig.json | 5 +- examples/lit/micro/vite.config.ts | 4 +- 16 files changed, 198 insertions(+), 167 deletions(-) diff --git a/examples/lit/micro/README.md b/examples/lit/micro/README.md index af33e35..afe1202 100644 --- a/examples/lit/micro/README.md +++ b/examples/lit/micro/README.md @@ -4,16 +4,16 @@ Minimal Lit 3 example using `texivia-router` for client-side routing. Demonstrat ## Routes -| Path | View | -|------|------| -| `/` | Redirects to `/{locale}/` based on `navigator.language` | -| `/{locale}/` | Landing | -| `/{locale}/login` | Login | -| `/{locale}/users/{id}/profile` | UserProfile | -| `/{locale}/about` | About | -| `/{locale}/imprint` | Imprint | -| `/{locale}/contact` | Contact | -| `*` | NotFound | +| Path | View | +| ------------------------------ | ------------------------------------------------------- | +| `/` | Redirects to `/{locale}/` based on `navigator.language` | +| `/{locale}/` | Landing | +| `/{locale}/login` | Login | +| `/{locale}/users/{id}/profile` | UserProfile | +| `/{locale}/about` | About | +| `/{locale}/imprint` | Imprint | +| `/{locale}/contact` | Contact | +| `*` | NotFound | ## How It Works @@ -21,7 +21,7 @@ Minimal Lit 3 example using `texivia-router` for client-side routing. Demonstrat ```ts // src/router.ts -export type View = (params: Record) => TemplateResult +export type View = (params: Record) => TemplateResult; export const router = new Router([ { path: '/', handler: () => `/${navigator.language}/` }, @@ -29,7 +29,7 @@ export const router = new Router([ { path: '/{locale}/users/{id:\\d+}/profile', view: renderUserProfile }, // ... { path: '*', view: renderNotFound }, -]) +]); ``` `` is the root LitElement. It listens for the `texivia` event, swaps the current view, and re-renders: @@ -38,31 +38,33 @@ export const router = new Router([ // src/app-shell.ts @customElement('app-shell') export class AppShell extends LitElement { - createRenderRoot() { return this } // light DOM + createRenderRoot() { + return this; + } // light DOM - @state() private view: View = renderLanding - @state() private params: Record = { locale: 'en' } + @state() private view: View = renderLanding; + @state() private params: Record = { locale: 'en' }; private onNavigate = (e: Event) => { - const detail = (e as CustomEvent).detail - if (detail?.view) this.view = detail.view - this.params = detail?.params ?? {} - } + const detail = (e as CustomEvent).detail; + if (detail?.view) this.view = detail.view; + this.params = detail?.params ?? {}; + }; connectedCallback() { - super.connectedCallback() - document.addEventListener('texivia', this.onNavigate) - router.start() + super.connectedCallback(); + document.addEventListener('texivia', this.onNavigate); + router.start(); } disconnectedCallback() { - super.disconnectedCallback() - document.removeEventListener('texivia', this.onNavigate) - router.stop() + super.disconnectedCallback(); + document.removeEventListener('texivia', this.onNavigate); + router.stop(); } render() { - return this.view(this.params) + return this.view(this.params); } } ``` diff --git a/examples/lit/micro/src/app-shell.ts b/examples/lit/micro/src/app-shell.ts index 8010f3c..158d349 100644 --- a/examples/lit/micro/src/app-shell.ts +++ b/examples/lit/micro/src/app-shell.ts @@ -1,39 +1,41 @@ -import { LitElement } from 'lit' -import { customElement, state } from 'lit/decorators.js' -import { router, type View } from './router' -import { renderLanding } from './pages/landing' +import { LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { router, type View } from './router'; +import { renderLanding } from './pages/landing'; @customElement('app-shell') export class AppShell extends LitElement { // Light DOM: lets global app.css apply and lets clicks bubble to // document so Texivia's click interception sees them. - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @state() private view: View = renderLanding - @state() private params: Record = { locale: 'en' } + @state() private view: View = renderLanding; + @state() private params: Record = { locale: 'en' }; private onNavigate = (event: Event) => { - const detail = (event as CustomEvent).detail - if (detail?.view) this.view = detail.view - this.params = detail?.params ?? {} - } + const detail = (event as CustomEvent).detail; + if (detail?.view) this.view = detail.view; + this.params = detail?.params ?? {}; + }; connectedCallback() { - super.connectedCallback() + super.connectedCallback(); // Listener must precede start(): for routes with no handler, _navigate // dispatches the texivia event synchronously inside start(), so attaching // afterwards loses the initial event on deep-link loads. - document.addEventListener('texivia', this.onNavigate) - router.start() + document.addEventListener('texivia', this.onNavigate); + router.start(); } disconnectedCallback() { - super.disconnectedCallback() - document.removeEventListener('texivia', this.onNavigate) - router.stop() + super.disconnectedCallback(); + document.removeEventListener('texivia', this.onNavigate); + router.stop(); } render() { - return this.view(this.params) + return this.view(this.params); } } diff --git a/examples/lit/micro/src/components/app-footer.ts b/examples/lit/micro/src/components/app-footer.ts index fbe8b74..e8a89f6 100644 --- a/examples/lit/micro/src/components/app-footer.ts +++ b/examples/lit/micro/src/components/app-footer.ts @@ -1,11 +1,13 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; @customElement('app-footer') export class AppFooter extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { return html` @@ -17,6 +19,6 @@ export class AppFooter extends LitElement { Contact - ` + `; } } diff --git a/examples/lit/micro/src/components/app-header.ts b/examples/lit/micro/src/components/app-header.ts index 2acf924..ae0d140 100644 --- a/examples/lit/micro/src/components/app-header.ts +++ b/examples/lit/micro/src/components/app-header.ts @@ -1,11 +1,13 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; @customElement('app-header') export class AppHeader extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { return html` @@ -17,6 +19,6 @@ export class AppHeader extends LitElement { Profile - ` + `; } } diff --git a/examples/lit/micro/src/layouts/main-layout.ts b/examples/lit/micro/src/layouts/main-layout.ts index 9fabc6d..df97cd8 100644 --- a/examples/lit/micro/src/layouts/main-layout.ts +++ b/examples/lit/micro/src/layouts/main-layout.ts @@ -1,9 +1,9 @@ -import { html, type TemplateResult } from 'lit' -import '../components/app-header' -import '../components/app-footer' +import { html, type TemplateResult } from 'lit'; +import '../components/app-header'; +import '../components/app-footer'; export const mainLayout = (locale: string, body: TemplateResult) => html`
${body}
-` +`; diff --git a/examples/lit/micro/src/main.ts b/examples/lit/micro/src/main.ts index 8e2fa18..e5d5e70 100644 --- a/examples/lit/micro/src/main.ts +++ b/examples/lit/micro/src/main.ts @@ -1,4 +1,4 @@ -import './app.css' -import './app-shell' +import './app.css'; +import './app-shell'; -document.getElementById('app')!.appendChild(document.createElement('app-shell')) +document.getElementById('app')!.appendChild(document.createElement('app-shell')); diff --git a/examples/lit/micro/src/pages/about.ts b/examples/lit/micro/src/pages/about.ts index 66f1d27..8293465 100644 --- a/examples/lit/micro/src/pages/about.ts +++ b/examples/lit/micro/src/pages/about.ts @@ -1,21 +1,25 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' -import { mainLayout } from '../layouts/main-layout' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; @customElement('page-about') export class PageAbout extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { - return mainLayout(this.locale, html` -

About

-

Texivia is a lightweight, framework-agnostic router for single-page applications.

-

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

- `) + return mainLayout( + this.locale, + html` +

About

+

Texivia is a lightweight, framework-agnostic router for single-page applications.

+

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

+ ` + ); } } -export const renderAbout = (p: Record) => - html`` +export const renderAbout = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/contact.ts b/examples/lit/micro/src/pages/contact.ts index ed18c7d..f8a8a80 100644 --- a/examples/lit/micro/src/pages/contact.ts +++ b/examples/lit/micro/src/pages/contact.ts @@ -1,21 +1,25 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' -import { mainLayout } from '../layouts/main-layout' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; @customElement('page-contact') export class PageContact extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { - return mainLayout(this.locale, html` -

Contact

-

Have questions or feedback? Reach out to us.

-

Email: example@texivia.dev

- `) + return mainLayout( + this.locale, + html` +

Contact

+

Have questions or feedback? Reach out to us.

+

Email: example@texivia.dev

+ ` + ); } } -export const renderContact = (p: Record) => - html`` +export const renderContact = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/imprint.ts b/examples/lit/micro/src/pages/imprint.ts index 9e61686..b6f8e71 100644 --- a/examples/lit/micro/src/pages/imprint.ts +++ b/examples/lit/micro/src/pages/imprint.ts @@ -1,23 +1,27 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' -import { mainLayout } from '../layouts/main-layout' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; @customElement('page-imprint') export class PageImprint extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { - return mainLayout(this.locale, html` -

Imprint

-

Responsible

-

Texivia Example App
123 Example Street
12345 Example City

-

Contact

-

Email: example@texivia.dev

- `) + return mainLayout( + this.locale, + html` +

Imprint

+

Responsible

+

Texivia Example App
123 Example Street
12345 Example City

+

Contact

+

Email: example@texivia.dev

+ ` + ); } } -export const renderImprint = (p: Record) => - html`` +export const renderImprint = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/landing.ts b/examples/lit/micro/src/pages/landing.ts index 30b0394..fbfa304 100644 --- a/examples/lit/micro/src/pages/landing.ts +++ b/examples/lit/micro/src/pages/landing.ts @@ -1,24 +1,28 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' -import { mainLayout } from '../layouts/main-layout' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; @customElement('page-landing') export class PageLanding extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { - return mainLayout(this.locale, html` -

Welcome to Texivia

-

Your one-stop solution for all your text processing needs.

-

Locale: ${this.locale}

-

Explore our features and services tailored just for you.

-

Get started by signing up or logging in!

-

Already have an account? Login here

- `) + return mainLayout( + this.locale, + html` +

Welcome to Texivia

+

Your one-stop solution for all your text processing needs.

+

Locale: ${this.locale}

+

Explore our features and services tailored just for you.

+

Get started by signing up or logging in!

+

Already have an account? Login here

+ ` + ); } } -export const renderLanding = (p: Record) => - html`` +export const renderLanding = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/login.ts b/examples/lit/micro/src/pages/login.ts index 79c91e7..6008dd6 100644 --- a/examples/lit/micro/src/pages/login.ts +++ b/examples/lit/micro/src/pages/login.ts @@ -1,27 +1,31 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' -import { mainLayout } from '../layouts/main-layout' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; @customElement('page-login') export class PageLogin extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' + @property() locale = 'en'; render() { - return mainLayout(this.locale, html` -

Login

- - `) + return mainLayout( + this.locale, + html` +

Login

+ + ` + ); } } -export const renderLogin = (p: Record) => - html`` +export const renderLogin = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/not-found.ts b/examples/lit/micro/src/pages/not-found.ts index 173839e..e642598 100644 --- a/examples/lit/micro/src/pages/not-found.ts +++ b/examples/lit/micro/src/pages/not-found.ts @@ -1,9 +1,11 @@ -import { LitElement, html } from 'lit' -import { customElement } from 'lit/decorators.js' +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; @customElement('page-not-found') export class PageNotFound extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } render() { return html` @@ -12,9 +14,8 @@ export class PageNotFound extends LitElement {

The page you are looking for does not exist.

Return to the home page.

- ` + `; } } -export const renderNotFound = () => - html`` +export const renderNotFound = () => html``; diff --git a/examples/lit/micro/src/pages/user-profile.ts b/examples/lit/micro/src/pages/user-profile.ts index dd3224a..f37bbf3 100644 --- a/examples/lit/micro/src/pages/user-profile.ts +++ b/examples/lit/micro/src/pages/user-profile.ts @@ -1,22 +1,27 @@ -import { LitElement, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' -import { mainLayout } from '../layouts/main-layout' +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; @customElement('page-user-profile') export class PageUserProfile extends LitElement { - createRenderRoot() { return this } + createRenderRoot() { + return this; + } - @property() locale = 'en' - @property() id = '' + @property() locale = 'en'; + @property() id = ''; render() { - return mainLayout(this.locale, html` -

User Profile

-

Locale: ${this.locale}

-

User ID: ${this.id}

- `) + return mainLayout( + this.locale, + html` +

User Profile

+

Locale: ${this.locale}

+

User ID: ${this.id}

+ ` + ); } } export const renderUserProfile = (p: Record) => - html`` + html``; diff --git a/examples/lit/micro/src/router.ts b/examples/lit/micro/src/router.ts index f097d99..9410787 100644 --- a/examples/lit/micro/src/router.ts +++ b/examples/lit/micro/src/router.ts @@ -1,14 +1,14 @@ -import { Router } from 'texivia-router' -import type { TemplateResult } from 'lit' -import { renderLanding } from './pages/landing' -import { renderLogin } from './pages/login' -import { renderUserProfile } from './pages/user-profile' -import { renderAbout } from './pages/about' -import { renderImprint } from './pages/imprint' -import { renderContact } from './pages/contact' -import { renderNotFound } from './pages/not-found' +import { Router } from 'texivia-router'; +import type { TemplateResult } from 'lit'; +import { renderLanding } from './pages/landing'; +import { renderLogin } from './pages/login'; +import { renderUserProfile } from './pages/user-profile'; +import { renderAbout } from './pages/about'; +import { renderImprint } from './pages/imprint'; +import { renderContact } from './pages/contact'; +import { renderNotFound } from './pages/not-found'; -export type View = (params: Record) => TemplateResult +export type View = (params: Record) => TemplateResult; export const router = new Router([ { path: '/', handler: () => `/${navigator.language}/` }, @@ -19,4 +19,4 @@ export const router = new Router([ { path: '/{locale}/imprint', view: renderImprint }, { path: '/{locale}/contact', view: renderContact }, { path: '*', view: renderNotFound }, -]) +]); diff --git a/examples/lit/micro/tsconfig.json b/examples/lit/micro/tsconfig.json index 1ffef60..d32ff68 100644 --- a/examples/lit/micro/tsconfig.json +++ b/examples/lit/micro/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/examples/lit/micro/vite.config.ts b/examples/lit/micro/vite.config.ts index 4f1b25a..c049f46 100644 --- a/examples/lit/micro/vite.config.ts +++ b/examples/lit/micro/vite.config.ts @@ -1,3 +1,3 @@ -import { defineConfig } from 'vite' +import { defineConfig } from 'vite'; -export default defineConfig({}) +export default defineConfig({}); From bb16d975f1045b92f2d1f2fb0b2dbb6072e2be7a Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Thu, 7 May 2026 20:12:15 +0200 Subject: [PATCH 18/23] clarify Light DOM rationale in Lit example app-shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous comment said Light DOM lets clicks "bubble to document" — but composed click events bubble through shadow boundaries fine. The actual constraint is event.target retargeting: with Shadow DOM, target becomes the host (), so the router's document-level closest('a') returns null and skips the navigation. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/app-shell.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/lit/micro/src/app-shell.ts b/examples/lit/micro/src/app-shell.ts index 158d349..943a3b4 100644 --- a/examples/lit/micro/src/app-shell.ts +++ b/examples/lit/micro/src/app-shell.ts @@ -5,8 +5,9 @@ import { renderLanding } from './pages/landing'; @customElement('app-shell') export class AppShell extends LitElement { - // Light DOM: lets global app.css apply and lets clicks bubble to - // document so Texivia's click interception sees them. + // Light DOM keeps the clicked as event.target so the router's + // document-level closest('a') resolves it; Shadow DOM would retarget + // target to . Also lets global app.css apply. createRenderRoot() { return this; } From 62439e72e458b57bcefd97f183eff9bca1a18bdc Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Thu, 7 May 2026 20:12:33 +0200 Subject: [PATCH 19/23] seed initial locale from navigator.language in Lit example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lit renders the app-shell before router.start()'s async chain dispatches the first texivia event, so the first paint uses whatever the @state default is. The previous hardcoded 'en' caused a brief mismatch on non-English browsers — header/footer links flickered as /en/ before the '/' handler redirect resolved to e.g. /de-DE/. Seeding from navigator.language aligns the initial paint with the redirect target. Subtags are stripped to keep the seed a plain language code; the handler still uses the full BCP-47 tag. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/app-shell.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/lit/micro/src/app-shell.ts b/examples/lit/micro/src/app-shell.ts index 943a3b4..c4a928b 100644 --- a/examples/lit/micro/src/app-shell.ts +++ b/examples/lit/micro/src/app-shell.ts @@ -13,7 +13,9 @@ export class AppShell extends LitElement { } @state() private view: View = renderLanding; - @state() private params: Record = { locale: 'en' }; + @state() private params: Record = { + locale: navigator.language.split('-')[0], + }; private onNavigate = (event: Event) => { const detail = (event as CustomEvent).detail; From 108dd2b4f68e9a7d5ae62b2859aadd613400781b Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Thu, 7 May 2026 20:13:02 +0200 Subject: [PATCH 20/23] document Light DOM requirement for Lit integration The README's Lit 3 section claimed plain tags are intercepted automatically, but that's only true with Light DOM. With Shadow DOM (LitElement's default), event.target retargets to the host and the router's closest('a') misses the click. Updates: - Spell out the Light DOM constraint in the trailing summary so copy-pasters don't omit createRenderRoot() and silently break navigation. - Reword the snippet's createRenderRoot() comment to match the example. - Seed params.locale from navigator.language so the README snippet doesn't render empty-locale hrefs on first paint. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 82b2248..ef5383a 100644 --- a/README.md +++ b/README.md @@ -162,11 +162,15 @@ import { renderLanding } from './pages/landing'; @customElement('app-shell') export class AppShell extends LitElement { - // Light DOM: lets global CSS apply and lets clicks bubble to document. + // Light DOM keeps the clicked as event.target so the router's + // document-level closest('a') resolves it; Shadow DOM would retarget + // target to the host. Also lets global CSS apply. createRenderRoot() { return this; } @state() private view: View = renderLanding; - @state() private params: Record = {}; + @state() private params: Record = { + locale: navigator.language.split('-')[0], + }; private onNavigate = (e: Event) => { const detail = (e as CustomEvent).detail; @@ -222,7 +226,7 @@ import { router } from './router'; router.navigate('/dashboard'); ``` -Plain `` tags inside Lit templates are intercepted automatically — no `` wrapper needed. See [`examples/lit/micro`](examples/lit/micro) for a complete example with layouts, parameterized routes, and a 404 fallback. +Plain `` tags inside Lit templates are intercepted automatically — no `` wrapper needed, **provided the host renders in Light DOM** (`createRenderRoot() { return this; }`). Shadow DOM retargets click events to the host, so `` inside a shadow root is invisible to the document-level click handler. See [`examples/lit/micro`](examples/lit/micro) for a complete example with layouts, parameterized routes, and a 404 fallback. ## Vue 3 From 98e18e087c2ad5cc56f6d7af613325272ccb7036 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Thu, 7 May 2026 20:18:16 +0200 Subject: [PATCH 21/23] strip locale subtag in Lit example router redirect The '/' handler redirected to /${navigator.language}/, preserving the full BCP-47 tag (e.g. /de-DE/). The app-shell @state seed strips the subtag, so a de-DE browser briefly rendered links with locale=de before the redirect landed on /de-DE/ and re-rendered with locale=de-DE. Stripping the subtag in the handler too keeps the seed and post-redirect params identical, eliminating the second re-render. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit/micro/src/router.ts b/examples/lit/micro/src/router.ts index 9410787..c9d33c5 100644 --- a/examples/lit/micro/src/router.ts +++ b/examples/lit/micro/src/router.ts @@ -11,7 +11,7 @@ import { renderNotFound } from './pages/not-found'; export type View = (params: Record) => TemplateResult; export const router = new Router([ - { path: '/', handler: () => `/${navigator.language}/` }, + { path: '/', handler: () => `/${navigator.language.split('-')[0]}/` }, { path: '/{locale}/', view: renderLanding }, { path: '/{locale}/login', view: renderLogin }, { path: '/{locale}/users/{id:\\d+}/profile', view: renderUserProfile }, From adeeb382fd93b4c82d8a222c973b6e3719fa25f2 Mon Sep 17 00:00:00 2001 From: Stefan Kopco Date: Thu, 7 May 2026 20:18:39 +0200 Subject: [PATCH 22/23] stop login form from triggering full page POST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The form had method="POST" action="/${locale}/login" but no backend exists. Submitting did a real navigation to a 404 — surprising for an SPA example. Replaced with a noop @submit handler so the form stays inert (the example demos routing, not auth). Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/lit/micro/src/pages/login.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit/micro/src/pages/login.ts b/examples/lit/micro/src/pages/login.ts index 6008dd6..225b08f 100644 --- a/examples/lit/micro/src/pages/login.ts +++ b/examples/lit/micro/src/pages/login.ts @@ -15,7 +15,7 @@ export class PageLogin extends LitElement { this.locale, html`

Login

-