You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 12eaab1
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: .claude/skills/verify-debatelab/features/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -53,7 +53,7 @@ Each feature file starts with an H1 title and one paragraph describing the user-
53
53
- Account: output language reaching the model from a new device, flowchart node delete (at once, with an `Undo` toast; no confirm dialog), save and load, and the speech timer (`R` resets only while paused) (`recipes/account.spec.ts`).
54
54
-[Usage limits](./usage-limits.md): Opus refused when its weight does not fit today while Haiku still runs, and the weekly AI budget dialog (`recipes/usage-limits.spec.ts`).
55
55
- Billing: expired account blocked, promo code restores access, and a discount code claimed once (a second Apply does not count again) stays in the pricing dialog and is sent with the upgrade (`recipes/billing.spec.ts`); each billing state (trial, extended access, active Pro, Pro set to cancel, promo Pro without Stripe, payment failed, subscription ended) shown the same way by Settings, the pricing dialog (its one plan button and Current badge) and a refused AI request, in English and Hungarian, light and dark, at 360 and 375 px (mobile project) and 768 and 1280 px (desktop project); the usage line on every tool page matching Settings and updating after a run; the trial banner; and `/app/billing` landing on the Settings subscription card (`recipes/billing-states.spec.ts`). The pricing dialog's Manage path stubs `create-portal` with its own no-subscription answer, since the portal needs Stripe; signed, forged, and replayed Stripe webhooks, including a failed renewal that keeps Pro (`e2e/full-stack/billing.spec.ts`).
56
-
-[Localization](./localization.md): tab titles per route and language, translated form validation and server refusals, the language switcher on sign-in and 404, and `?lang=` with the `/es` and `/hu`home pages (`recipes/localization.spec.ts`).
56
+
-[Localization](./localization.md): tab titles per route and language, translated form validation and server refusals, the language switcher on sign-in and 404, and `?lang=` with the `/es` and `/hu` pages (`recipes/localization.spec.ts`).
57
57
- Admin announcements and the non-admin block (`e2e/full-stack/admin.spec.ts`), public share links (`e2e/full-stack/public.spec.ts`), and Workspaces with live collaboration and invites (`e2e/workspace-collaboration.spec.ts`).
58
58
59
59
Not yet mapped in recipes: theme, the public landing and SEO pages beyond their titles, the free no-login tools under `/tools` and `/embed/debate-timer` (no backend; `e2e/free-tools.spec.ts` covers them), and share-link creation (the app has no client caller for the `share` function). The agentic suite covers UI language switching (`agentic/workspace.e2e.ts`) and renders the public pages in every language (`agentic/pages.e2e.ts`).
Copy file name to clipboardExpand all lines: .claude/skills/verify-debatelab/features/localization.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,13 @@
1
1
# Localization
2
2
3
-
The interface runs in English, Spanish and Hungarian. Every page names itself in the tab title in the interface language ("Generálás · DebateLab" in the app, "Felhasználási feltételek | DebateLab by Pi-Stack" on public pages), form validation and server refusals read in that language, the language switcher sits in every header, and `?lang=es|hu|en` links and the `/es` and `/hu` home pages open the app in that language.
3
+
The interface runs in English, Spanish and Hungarian. Every page names itself in the tab title in the interface language ("Generálás · DebateLab" in the app, "Felhasználási feltételek | DebateLab by Pi-Stack" on public pages), form validation and server refusals read in that language, the language switcher sits in every header, and `?lang=es|hu|en` links and the Hungarian and Spanish pages (`/hu`, `/es`, `/hu/tools/…`, `/es/tools/…`) open the app in that language.
4
4
5
5
## Sub-features
6
6
7
7
- loc-titles: `document.title` on app and public routes, per language, and a switch retitling the page without a reload. App routes read `<page> · DebateLab` (`usePrivateRouteHead`); public pages use the `pageHead` catalog section (`usePageHead`), whose English text is also what the prerender writes.
8
8
- loc-validation: an empty Attack submit shows `validation.caseTextRequired` in the interface language, and nothing shows before the first submit. Generate, Brainstorm and Factoids use the same keys.
9
9
- loc-switcher: the switcher on `/login`, the 404 page, and the other public pages (`PublicPageShell`), besides the landing page and the app header.
10
-
- loc-links: `/?lang=hu` switches and saves the language, sets `<html lang>`, and drops the parameter; `/es` and `/hu`land on `/` in that language (a client route here; `vercel.json` redirects them on the host).
10
+
- loc-links: `/?lang=hu` switches and saves the language, sets `<html lang>`, and drops the parameter; `/es` and `/hu`are prerendered pages of their own that keep their address, and Start from `/hu` leads into the app in Hungarian.
11
11
- loc-server-error: a refused promo code shows `serverErrors.promoInvalid` in the interface language while redeem-promo still answers in English beside its `code`.
- The switcher's own name is translated (`langSwitcher.label`); match it in all three languages.
31
31
- The recipe reads the catalogs from `src/locales/`, so its expectations follow the strings. Match on keys, never on copied text.
32
32
- The promo field shows only for an account that would start a Checkout, so the recipe marks its Pro demo user expired first.
33
-
-`/es` and `/hu`on the host are a `vercel.json` redirect; the local stack proves only the client route.
33
+
-`/es` and `/hu`are files in `dist/` (`hu/index.html`), listed with their tool pages in `src/config/localized-routes.json`. Choosing another language with the switcher on one of them moves to that language's address.
Copy file name to clipboardExpand all lines: docs/operations.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,11 +54,13 @@ After `vite build`, [`scripts/prerender.mjs`](../scripts/prerender.mjs) renders
54
54
55
55
The free tools under `/tools` ([`src/pages/tools/`](../src/pages/tools/)) are prerendered the same way, listed in [`src/prerender/tool-pages.tsx`](../src/prerender/tool-pages.tsx) with heads in [`src/lib/seo/tool-pages.ts`](../src/lib/seo/tool-pages.ts). They render their own JSON-LD in the page body, so the dev server and the prerendered HTML carry the same structured data, and their `dateModified` is `TOOL_PAGES_MODIFIED` in that file: bump it with a substantive content change. Their speech times come from [`src/lib/free-tools/formats.ts`](../src/lib/free-tools/formats.ts), which cites a league source per format.
56
56
57
+
The landing page and the free tools also exist in Hungarian and Spanish, each at its own address: `/hu`, `/es`, and the tools under translated slugs such as `/hu/tools/vitaidomero` and `/es/tools/cronometro-debate`. [`src/config/localized-routes.json`](../src/config/localized-routes.json) is the one list of those addresses; the router, the prerender, `check-site` and `llms.txt` read it. The prerender renders each one in its language, with `<html lang>`, a self-canonical, and `hreflang` links for `en`, `hu`, `es` and `x-default` (English) on all three versions; pages without translations get no `hreflang`. Heads come from the `pageHead` catalog section in that language. The translated tool pages reuse the English widgets, take their copy from [`src/pages/tools/localized/`](../src/pages/tools/localized/), and set `dateModified` from that copy's `modified` date. Opening a translated address chooses its language, like a `?lang=` link, so Start leads into the app in that language; choosing another language there moves to that language's address. The old homepage links `/?lang=hu` and `/?lang=es` get a `vercel.json` 301 to `/hu` and `/es`. Vercel appends the original query, so the browser arrives at `/hu?lang=hu`, which is canonical to `/hu`; the client drops `?lang=` on load. `?lang=` on any other page (`/app?lang=hu`) is not redirected.
58
+
57
59
`/embed/debate-timer` is a second Vite HTML entry ([`embed/debate-timer/index.html`](../embed/debate-timer/index.html)), not an app route: it loads only the timer, never the app shell, Jam, analytics or the service worker, so a site that frames it sees no third-party request. It is `noindex` with its canonical URL on `/tools/debate-timer`, and the service worker leaves `/embed/` alone.
58
60
59
-
[`scripts/sitemap.mjs`](../scripts/sitemap.mjs) then writes `dist/sitemap.xml` from every page in `dist/` that names itself as canonical and is not `noindex`. Each `<lastmod>` is the `dateModified` the page declares in its JSON-LD: the guide pages' `CONTENT_DATE` in [`scripts/generate_seo_content.py`](../scripts/generate_seo_content.py), the legal pages' "last updated" dates, the tool pages' `TOOL_PAGES_MODIFIED`, and the landing page's last source commit. A page without a date is listed without `<lastmod>`.
61
+
[`scripts/sitemap.mjs`](../scripts/sitemap.mjs) then writes `dist/sitemap.xml` from every page in `dist/` that names itself as canonical and is not `noindex`. A page with `hreflang` links in its head is listed with the same alternates as `xhtml:link` entries. Each `<lastmod>` is the `dateModified` the page declares in its JSON-LD: the guide pages' `CONTENT_DATE` in [`scripts/generate_seo_content.py`](../scripts/generate_seo_content.py), the legal pages' "last updated" dates, the tool pages' `TOOL_PAGES_MODIFIED`, and the landing page's last source commit. A page without a date is listed without `<lastmod>`.
60
62
61
-
[`scripts/check-site.mjs`](../scripts/check-site.mjs) runs next and fails the build when an internal link, a `#fragment`, a sitemap URL or an `llms.txt` URL does not resolve (it applies the `vercel.json` redirects and rewrites), when JSON-LD does not parse or carries HTML entities, when a FAQPage question is not visible on its page, when the home page's entity graph has an unresolved `@id`, a `WebSite` or `SoftwareApplication` not named for the product, or no `Organization.sameAs`, when a page's `publisher` or `about` names an `@id` the home page does not define, when an indexed page other than `/terms`, `/privacy`, and `/expocit/sources` lacks its question H2 (`#short-answer`) as the first H2, when a page's first heading is not its H1 or a heading skips a level (H1 to H3), or when an image has no alt text, a button has no name or a form field has no label.
63
+
[`scripts/check-site.mjs`](../scripts/check-site.mjs) runs next and fails the build when an internal link, a `#fragment`, a sitemap URL or an `llms.txt` URL does not resolve (it applies the `vercel.json` redirects and rewrites), when JSON-LD does not parse or carries HTML entities, when a FAQPage question is not visible on its page, when the home page's entity graph has an unresolved `@id`, a `WebSite` or `SoftwareApplication` not named for the product, or no `Organization.sameAs`, when a page's `publisher` or `about` names an `@id` the home page does not define, when an indexed page other than `/terms`, `/privacy`, and `/expocit/sources` lacks its question H2 (`#short-answer`) as the first H2, when a page's first heading is not its H1 or a heading skips a level (H1 to H3), when an image has no alt text, a button has no name or a form field has no label, or when a page's `hreflang` links do not resolve, leave out the page itself or `x-default`, disagree with `<html lang>`, or differ from the set on a page they name.
62
64
63
65
[`scripts/check-links.mjs`](../scripts/check-links.mjs) runs after it and fails the build when a sitemap page other than `/terms`, `/privacy` and `/expocit/sources` has fewer than 3 inbound links from other sitemap pages, or when the home page links to fewer than 15 distinct sitemap pages. `node scripts/check-links.mjs --report` prints every count after a build. The home page reaches the guides and tools through [`GuideHub.tsx`](../src/components/landing/GuideHub.tsx).
64
66
@@ -68,7 +70,7 @@ Every guide opens with the reader's main question as its first H2 and an answer
68
70
69
71
To retire a guide, delete its `PAGES` entry, its `CLUSTERS` and `LINK_PHRASES` entries, and its `public/<slug>/` folder (the generator never deletes), add its redirects to `vercel.json`, and repoint any link to it. Two guides that answer the same search should become one page, not two thin ones.
70
72
71
-
The site origin, product name, brand line, organization name and the organization's `sameAs` profiles live in [`src/config/site.json`](../src/config/site.json). The app's head tags, the prerender and sitemap scripts, and the guide generator all read it, and guide copy names the product with a `{product}` token and the Pro price with `{pro_price}`. The number of prep modes comes from the tool lists in [`tool-links.tsx`](../src/components/features/tools/tool-links.tsx): the app's copy reads `PREP_MODE_COUNT` and `UTILITY_COUNT`, and guide copy uses `{mode_count}`, `{mode_names}` and `{utility_names}`. Do not hardcode those strings in new code or copy. App strings name the product with `{{product}}`, `{{brand}}` and `{{host}}`, which [`src/i18n.ts`](../src/i18n.ts) fills from `site.json` for every key.
73
+
The site origin, product name, brand line, organization name and the organization's `sameAs` profiles live in [`src/config/site.json`](../src/config/site.json). The app's head tags, the prerender and sitemap scripts, and the guide generator all read it, and guide copy names the product with a `{product}` token and the Pro price with `{pro_price}`. The number of prep modes comes from the tool lists in [`tool-links.tsx`](../src/components/features/tools/tool-links.tsx): the app's copy reads `PREP_MODE_COUNT` and `UTILITY_COUNT`, and guide copy uses `{mode_count}`, `{mode_names}` and `{utility_names}`. The trial length and Pro price come from `pricing` in the same file: i18next passes `{{trialDays}}`, `{{proMonthly}}`, `{{product}}`, `{{brand}}` and `{{host}}` to every catalog string, so the English, Spanish and Hungarian pages state the same facts. Do not hardcode those strings in new code or copy. App strings name the product with `{{product}}`, `{{brand}}` and `{{host}}`, which [`src/i18n.ts`](../src/i18n.ts) fills from `site.json` for every key.
72
74
73
75
After a production deploy, and only with Adam's approval, `npm run seo:indexnow` sends every URL in the sitemap to IndexNow in one request to `api.indexnow.org`, which shares it with Bing, Yandex, Seznam, Naver and the other engines that take part (not Google). It reads `dist/sitemap.xml` when a build is present and the live sitemap otherwise. `-- --only /tools /` sends just those pages, and `-- --dry-run` prints the request without sending it. The key file is `public/<key>.txt`. It is not wired into CI.
0 commit comments