Open Questions & Chosen Defaults
This page collects open questions raised by docs, specs, plans, or agent runs that need maintainer review. Per Article IV of the constitution, agents must not block on unresolved questions — they should pick a sensible default, record it here, and keep moving. Maintainers come back later to confirm, override, or refine.
Format. Each question is a level-3 heading with a stable anchor id of the form
q-NNN-short-slug, e.g.### Q-002a SDK package name. The body has these sections:
- Context — what gave rise to the question.
- Options — bullet list of candidate answers.
- Default — which option the agent picked, in bold.
- Owner — who should review. Default: maintainers.
- Status —
open,confirmed,rejected,superseded.
Spec 002 — Plugin Architecture
Q-002a SDK package name
- Context. We need a name for the canonical plugin SDK package that every plugin will depend on.
- Options.
@ever-works/plugin-sdk— explicit; follows ecosystem conventions (@stripe/stripe-sdk,@aws-sdk/client-*).@ever-works/plugins— shorter; matches the runtime package@ever-works/plugin-runtimeand thepackages/plugin-*naming.@ever-works/plugin-api— describes the surface; less common convention.
- Default.
@ever-works/plugin-sdk. - Owner. Template maintainers.
- Status.
open.
Q-002b Plugin-to-plugin extensions in v1
- Context. Should a plugin be able to expose its own slots and capability interfaces to other plugins, or is that v2 only?
- Options.
- Yes, minimal. Allow re-exporting from
@ever-works/plugin-sdkso a plugin can add new capabilities, but keep the API tiny. - No. Plugins consume only the SDK-defined surface in v1.
- Yes, minimal. Allow re-exporting from
- Default. Yes, minimal.
- Owner. Template maintainers.
- Status.
open.
Q-002c Per-plugin config storage
- Context. Where do per-plugin configs live? Some adopters want source-of-truth in env vars; others want admin-editable configs.
- Options.
- DB row + override via env vars (env wins).
- Env vars only (admins must redeploy to change anything).
- DB only (env vars ignored if a row exists).
- Default. DB row + override via env vars.
- Owner. Template maintainers.
- Status.
open.
Spec 003 — Auth providers
Q-003a Add Passkey / WebAuthn?
- Context. Passkeys are increasingly expected.
- Options.
- Add as a built-in Auth.js provider in v1.
- Defer to a separate spec when the Auth.js Passkey support is GA-stable.
- Default. Defer.
- Owner. Template maintainers.
- Status.
open.
Spec 004 — Payment providers
Q-004a Allow multiple active providers per checkout?
- Context. Some adopters want to A/B test providers or run them in parallel (e.g. Stripe for cards, LemonSqueezy for VAT-handled countries).
- Options.
- Single active provider per session. Simpler.
- Routing rules per country / SKU.
- Default. Single active provider per session in v1; routing rules in a future spec.
- Owner. Template maintainers.
- Status.
open.
Spec 005 — Internationalisation
Q-005a Automated translation pipeline (DeepL / OpenAI)
- Context. Translating docs and UI strings is currently manual with PR-based review.
- Options.
- Add a CI step that proposes machine translations on every English update, with human review before merge.
- Stay manual, but maintain a translation status dashboard.
- Default. Stay manual in v1; revisit once
docs/internationalization/coverage.mdexists and shows real drift. - Owner. Template maintainers.
- Status.
open.
Spec 006 — Git-based CMS
Q-006a Hygraph adapter as built-in plugin?
- Context. Several adopters use Hygraph; an adapter could ship in the box.
- Options.
- Ship a Hygraph plugin under
packages/plugin-content-hygraph/once SDK 002 stabilises. - Leave as third-party.
- Ship a Hygraph plugin under
- Default. Wait until plugin SDK is stable, then ship.
- Owner. Template maintainers.
- Status.
open.
Spec 007 — Theming
Q-007a Logo upload from admin?
- Context. Admins currently set the logo via env / static assets.
- Options.
- Add an admin upload form using whatever object-store integration is configured (S3 / Supabase Storage).
- Defer, keep static assets.
- Default. Future plugin.
- Owner. Template maintainers.
- Status.
open.
Spec 008 — Analytics
Q-008a Consent banner integration
- Context. GDPR / CCPA compliance often requires a consent banner before loading analytics scripts.
- Options.
- Ship a built-in cookie banner with provider gating.
- Ship as a future plugin (
packages/plugin-consent-…/).
- Default. Future plugin.
- Owner. Template maintainers.
- Status.
open.
Spec 010 — E2E test coverage
Q-010a Worker count in CI
- Context. Playwright runs with
workers: 2in CI today; suite growth may push run-time too high. - Options.
- Keep
workers: 2, add sharding when needed. - Bump to
workers: 4and accept higher CI cost.
- Keep
- Default. Keep
workers: 2until measured wall time exceeds 20 minutes. - Owner. Template maintainers.
- Status.
open.
Q-010b Should /api/admin/roles and /api/admin/roles/active carry an explicit auth() gate?
- Context. Surfaced while writing
apps/web-e2e/tests/api/admin-roles-query.spec.ts(per-source-file reference:docs/plugins/admin-roles-query-spec.md). Every other admin-tree GET route covered by a sibling smoke spec callsauth()and short-circuits with 401 / 403 before any repository call. Theapps/web/app/api/admin/roles/route.tsGET handler does not callauth()and does not checksession?.user?.isAdminbefore delegating toroleRepository.findAllPaginated(...). The same absence holds for the siblingapps/web/app/api/admin/roles/active/route.tsGET handler, which delegates toroleRepository.findActive()with no auth check at all. Roles include theirpermissions[]array (which enumerates the security boundary of the admin tree), so leaking this list to anonymous callers narrows the surface a future attacker has to enumerate. A regression here is "open" rather than "broken" — the existingadmin-protected-extra.spec.tssmoke asserts only the loose< 500envelope, which passes both for a 200 response (current behaviour) AND for a 401 (post-fix behaviour). - Options.
- Add the same two-step gate as the sibling
/api/admin/roles/statsroute —if (!session?.user) return 401followed byif (!session.user.isAdmin) return 403. The matchingadmin-roles-query.spec.tssmoke spec is invariant to this fix and stays green. - Add a single-step gate that returns 401 for both
branches (matches the
/api/admin/clients//api/admin/comments//api/admin/companies//api/admin/usersshape). - Add the longer-message
'Unauthorized. Admin access required.'envelope (matches the/api/admin/sponsor-ads//api/admin/twenty-crm/configshape). - Leave the route open and document the
intentionally-public posture in a follow-up
apps/web/app/api/admin/roles/route.tsJSDoc block.
- Add the same two-step gate as the sibling
- Default. Add the two-step gate matching
/api/admin/roles/stats— uniform with the closest sibling route and minimal risk of widening or narrowing the existing client-side error-handling contract. - Owner. Template maintainers.
- Status.
open.
Spec 012 — Newsletter
Q-012a Persist subscribers in our DB?
- Context. Some providers (Resend, Mailchimp) own the subscriber list; others (e.g. Loops) require us to mirror.
- Options.
- Mirror via DB row for audit and offline reads.
- Provider as source of truth, no DB row.
- Default. Mirror in DB for audit.
- Owner. Template maintainers.
- Status.
open.
Spec 013 — Notifications
Q-013a Notifications source of truth
- Context. Novu owns the notification list, but UI offline reads benefit from a local mirror.
- Options.
- Mirror in DB.
- Provider as source of truth.
- Default. Mirror in DB.
- Owner. Template maintainers.
- Status.
open.
Spec 014 — Docs translation
Q-014a Translation hosting
- Context. Translations currently live in repo; some teams prefer Crowdin / Lokalise.
- Options.
- Keep in repo with PR review.
- Move to Crowdin and sync via CI.
- Default. Keep in repo.
- Owner. Template maintainers.
- Status.
open.
Spec 015 — Spec Kit adoption
Q-015a Automate spec coverage report
- Context. Each package / feature should have a spec; a script could enforce that.
- Options.
- Author
apps/web/scripts/lint-specs.tsthat fails CI when a package lacks a spec. - Manual review.
- Author
- Default. Manual for now, automate later.
- Owner. Template maintainers.
- Status.
open.
Spec 017 — Map view
Q-017a Hero on /map route?
- Context. The dedicated
/maproute shares the same composition as the listing's map view-toggle, but the visitor's intent at/mapis "show me the map", not "explore the homepage". Reusing the(listing)route group would inherit the homepage hero. - Options.
- Render
/mapfull-bleed without a hero (current implementation). - Reuse the
(listing)route group and ship the hero too.
- Render
- Default. Full-bleed, no hero. Visitors clicking the header Map link want to see the map immediately; the hero would push the map below the fold.
- Owner. Template maintainers.
- Status.
open.
Spec 018 — Performance Budget
Q-018a Run Lighthouse on every PR or only labelled ones?
- Context. Lighthouse CI on every PR adds CI minutes and slows
down feedback loops. Running it only on PRs labelled
perf-checkcontains cost but means contributors must opt in. - Options.
- Labelled-only (
perf-check). Maintainers opt in for perf-sensitive PRs. Nightly run ondevelopcatches drift. - Every PR. Heavier CI bill; faster regression detection.
- Periodic only (nightly + manual). Relies on bisecting when a regression is reported.
- Labelled-only (
- Default. Labelled-only (
perf-check) plus the nightlydeveloprun. Re-evaluate once we have CI minute usage data. - Owner. Template maintainers.
- Status.
open.
Q-018b Where does the perf budget file live?
- Context. A budget file shared across
apps/**works at the monorepo root; an app-specific file underapps/web/keeps the blast radius small but assumes a single app. - Options.
performance/budgets.jsonat the monorepo root.apps/web/performance/budgets.json.
- Default. Monorepo root (
performance/budgets.json) — future apps underapps/**reuse the same script and config. - Owner. Template maintainers.
- Status.
open.
Spec 019 — CDN-Cacheable Public Surface with Pluggable Locale Detection
Q-019a server-redirect value in YAML
- Context.
settings.i18n.locale_detectionacceptsclient-banner(default) andnone. Should it also acceptserver-redirect? - Options.
- Env var only.
LOCALE_DETECTION_MODE=server-redirectis the single switch. YAML stays focused on client-side concerns. - YAML accepts
server-redirecttoo. Operators can set it from the data repo without touching env vars; middleware reads YAML on every request.
- Env var only.
- Default. Env var only. Middleware needs to know at edge time, before any YAML is loaded; an env var is the cleaner shape.
- Owner. Template maintainers.
- Status.
open.
Q-019b Localized banner copy
- Context. The locale-suggestion banner currently shows English
copy (
"This page is also available in <NativeName>"). Should it be localized to the current page locale? - Options.
- Stay English. The banner is by definition shown when the visitor's preferred locale doesn't match the current page; the visitor likely understands English well enough to read a one-line suggestion, and the actionable CTA ("Switch to Français?") uses the native name.
- Localize via
messages/<locale>.json. Adds 21 new keys per locale and keeps the banner copy in the visitor's current page locale.
- Default. Stay English in v1, revisit when there's a user complaint.
- Owner. Template maintainers.
- Status.
open.
Spec 023 — AI Chat for Directory Visitors
Q-023a Default model
- Context. The chat is OpenAI-compatible by design but needs a sensible default model so a fresh template that opts in works without a model-selection UI.
- Options.
openai/gpt-4o-minivia OpenRouter. Cheap, fast, broadly multilingual; OpenRouter is the same gateway the Ever Works platform uses by default.anthropic/claude-3-5-haikuvia OpenRouter. Better quality on long context; slightly higher cost; same gateway.
- Default.
openai/gpt-4o-minivia OpenRouter. - Owner. Template maintainers.
- Status.
open.
Q-023b Should v1 allow chat-driven mutations?
- Context. The chat could navigate the visitor to a submission form and fill it in / submit it on their behalf via tool calls. Mutations multiply the abuse surface and prompt-injection blast radius.
- Options.
- Read-only + navigate. Tools fetch data and return a route to the visitor; the visitor confirms by clicking.
- Read + write. Tools can also call into existing submit/follow/favourite repositories with the visitor's session.
- Default. Read-only + navigate. Re-evaluate once the read-only tools have been used in anger.
- Owner. Template maintainers.
- Status.
open.
Q-023e Test runner for plugin-ai-chat (and possibly the wider repo)
- Context. The repo has no JS test framework today —
CLAUDE.md §4 says "Treat
pnpm lint,pnpm tsc --noEmit, andpnpm buildas the main 'test suite' (there is currently no Jest/Vitest setup)." But forplugin-ai-chatwe want real unit tests of the Zod schema, the tools, and the agent's scenario filter — typecheck alone can't catch a wrongly-named scenario or a missingrequiresAuthflag. - Options.
- Add
vitesttopackages/plugin-ai-chatonly. Small blast radius; doesn't commit the whole repo. Other packages that want it later opt in independently. - Add
vitestto the repo root + avitest.workspace.ts. Bigger change; would also need a separate spec since it affects every package, includingapps/web. - Skip unit tests entirely; lean on Playwright e2e (T-013) for coverage.
- Add
- Default. Add
vitesttoplugin-ai-chatonly (T-002b). The pure-TS schema + tool logic is exactly where unit tests pay back the most; Playwright is good enough for the rest. - Owner. Template maintainers.
- Status.
open.
Q-023d Slot IDs for chat surfaces
- Context. Plan §4's manifest stub uses
layout.global.overlay,hero.takeover,layout.sidebar.tab. None of these exist inpackages/plugin-sdk/src/slots.ts— the canonicalSLOT_IDSarray currently covers header / footer / item-detail / admin / client-dashboard slots only. The SDK file notes: "Slot ids are stable; renaming a slot is a breaking change. New slot ids land via a small spec." - Options.
- Add three new SLOT_IDS (
chat.launcher.overlay,home.hero.takeover,layout.sidebar.tab) topackages/plugin-sdk/src/slots.tsas a coordinated sub-task of T-001, called out in this spec since it is materially part of the chat feature surface. - Skip the slot system entirely for v1: mount
<ChatLauncher>directly fromapps/web/app/[locale]/layout.tsxbehind theaiChat.enabledconfig gate. Lose the plugin-discoverability story; gain simplicity.
- Add three new SLOT_IDS (
- Default. Add three new SLOT_IDS — preserves the
plugin-first principle (Article I) and keeps the
manifest stub honest. Adding new slot IDs lands in the
same PR series as T-001 / T-006, with the diff to
slots.tsdocumented in the relevant task's commit. - Owner. Template maintainers.
- Status.
open.
Q-023c Where do scenario openers and the system prompt live?
- Context. Strings need to translate via the existing pipeline
(
next-intl+ Crowdin per Spec 014) but they're conceptually part of the plugin package. - Options.
apps/web/messages/<locale>.jsonunderAI_CHAT_*. The plugin package reads them throughnext-intlat call time; Crowdin/translators see them like any other UI string.- Inline
.tsconstants underpackages/plugin-ai-chat/src/prompts/<locale>.ts. Keeps the plugin self-contained but bypasses the translation pipeline.
- Default.
apps/web/messages/<locale>.jsonunderAI_CHAT_*. - Owner. Template maintainers.
- Status.
open.
Spec 044 — Public payment config served at runtime
Q-044a Should [locale]/layout.tsx also pass the public payment config as a server prop?
- Context. Spec 044 serves the browser-safe payment config from
GET /api/payment/public-configand a React Query hook whoseinitialDatais the build-time env. On platform-deployed k8s builds the bundle has no inlinedNEXT_PUBLIC_*, so the first client render sees an empty config and the UI updates once the fetch settles (one round-trip;usePaymentAvailabilityhides the flip by keeping its SSR default until then). Threading the server-computed config through[locale]/layout.tsx→Providerswould remove that round-trip and make SSR + first client render identical in every deployment mode. - Options.
- Route + hook only (current). One tiny fetch per page load, same
pattern as
useStripeProducts, and it also serves modals/hooks rendered outside the locale tree (rootapp/layout.tsx). - Route + hook and a server prop seeded into the hook's
initialDataviaProviders. Zero-request first paint; slightly more plumbing and two sources to keep consistent.
- Route + hook only (current). One tiny fetch per page load, same
pattern as
- Default. Route + hook only. Revisit if the post-fetch re-render is visible in the field.
- Owner. Template maintainers.
- Status.
open.
Spec 046 — Provider-aware pricing configuration in works.yml
Q-046a Should provider: manual render a distinct pricing surface?
- Context. EW-131 asks
works.ymlto acceptprovider: manual— "show the prices, take payment elsewhere". Spec 046 accepts the value and suppresses the in-site checkout:handleCheckout()returns before any gateway branch and logs the reason. Which cards render is unchanged — still the LIVE / DEMO logic of spec 044. That is safe but silent: a manual-checkout operator arguably wants a "Contact us" call to action on the paid cards rather than a button that does nothing. - Options.
- Suppress the checkout and render the existing cards (current). One guard in the pricing flow, no new strings, no new localisation work.
- Add a manual-checkout mode: a per-plan contact URL in
works.ymland a dedicated CTA on the card. Needs new i18n keys in all locales and a new branch inuse-pricing-section.ts.
- Default. Suppress the checkout, keep the existing cards. The value is accepted and documented now; the UX affordance can land on its own ticket once someone actually runs manual checkout.
- Owner. Template maintainers.
- Status.
open.
Q-046b Should a malformed pricing: block ever be fatal?
- Context. Spec 046 logs each problem and falls back to the built-in
plans.
getConfig()runs on every render, so throwing would take a whole directory offline over a typo in an optional block. - Options.
- Warn and fall back (current). The site stays up; the operator sees
[CONTENT] Invalid "pricing" section …with one line per field. - Fail the build (not the request) when the data repository is cloned at build time, so the typo is caught before deploy.
- Warn and fall back (current). The site stays up; the operator sees
- Default. Warn and fall back. Revisit if operators report missing the log line; a build-time check is additive and can land later.
- Owner. Template maintainers.
- Status.
open.
Spec 047 — Markdown mirrors reachable (private-folder routing fix)
Spec 051 — Admin billing issues
Q-051-1 Should a refund carry a provider-side idempotency key?
- Context.
refundBillingIssue()takes an atomic claim on the issue row (billing_issues.refund_claimed_at, a single conditional UPDATE) before it callsPaymentProviderInterface.refundPayment, and deliberately does NOT hand the claim back when the provider call throws — a lost response and a clean rejection are indistinguishable, so releasing it would let a retry submit a second refund for a charge that may already be refunded. The claim instead expires afterREFUND_CLAIM_TTL_MS(5 minutes) so a crashed request cannot strand the issue forever. That expiry is the one remaining theoretical window: a provider call still in flight when the TTL elapses could be joined by a second claim, and the row-levelonlyWithClaimguard on the write can stop the second request from recording over the first, but not from placing the call. - Options.
- Leave it (current). The window requires a single provider HTTP request to still be open five minutes after it started — longer than every adapter's own SDK timeout and than the serverless function budget the template targets — and it needs a second admin to press refund inside exactly that window. The cost of the alternative reading (never reclaim) is an issue permanently locked by any crashed request.
- Add an idempotency key to the provider seam. Widen
PaymentProviderInterface.refundPayment(paymentId, amount?)with an optional idempotency key derived from the billing issue, and thread it through the Stripe, Polar, Solidgate and LemonSqueezy adapters pluspayment-service.ts. Stripe and Polar both honour one; this closes the window completely rather than narrowing it. - Lengthen
REFUND_CLAIM_TTL_MS. Cheapest, and strictly worse on the other axis: it narrows the duplicate window only by widening the stranded window.
- Default. Leave it, and fix it properly in the payment-provider spec. Spec 051 states it adds no new payment abstraction, and an idempotency parameter is a change to the shared provider interface and all four adapters — it belongs with the seam it changes, not inside an admin feature.
Spec 053 — Email two-factor authentication
Q-053a Should admin users rows be able to enable email 2FA as well?
- Context. Spec 047 stores
two_factor_enabled, the failed-attempt counter and the lock timestamp onclient_profiles, and surfaces the toggle on/client/settings/security. An admin signs in through the same credentials provider but has ausersrow with an admin role and no client profile, so today the 2FA branch inlib/auth/credentials.tssimply never fires for them. Admins are the higher-value target, so the asymmetry is worth an explicit decision rather than an accident of where the column happened to live. - Options.
- Client profiles only (current). One storage location, one settings
surface, no schema change to
users. Admin accounts in this template are few and typically operator-managed. - Mirror the three columns onto
users, add an admin-side settings card, and branch on whichever row exists. Broader protection, but it doubles the storage location for the same concept and needs a second settings surface under/admin. - Move the columns to
usersfor everyone and read through the account rather than the profile. Cleanest long-term shape, but it is a migration of a column that already ships onclient_profilesand is read by three admin query projections and the admin advanced search.
- Client profiles only (current). One storage location, one settings
surface, no schema change to
- Default. Client profiles only. Revisit together with any future
second factor (TOTP / WebAuthn), which would want a single
user-level factor registry anyway — that is the right moment to pay the migration. - Owner. Template maintainers.
- Status.
open.
Spec 052 — Admin payment reports and export
Q-052-1 Should the payment report also export PDF?
- Context. EW-117 asks for
"tools to filter and export reports (CSV, PDF, etc.)".
apps/webhas no PDF generation dependency —exceljsandpapaparseare the only document libraries inapps/web/package.json, and both were already there for the item export. Article VII (reuse before build) andAGENTS.md§14 ("ask the user first before adding new dependencies") both point away from pulling a PDF engine in as a side effect of this feature. - Options.
- CSV + XLSX only (current). No new dependency. XLSX already covers the "hand it to a stakeholder" case, and a spreadsheet is more useful than a PDF for revenue numbers because the recipient can re-sort and sum it.
- Add a client-side PDF library (
jspdf+jspdf-autotable, ~350 KB). Renders in the browser from the rows already loaded, so no server cost — but only the current page of rows, not the full filtered set. - Add a server-side renderer (
@react-pdf/renderer, or headless Chromium). Full fidelity over the whole filtered set; a heavy dependency, and headless Chromium is not viable in the template's serverless targets.
- Default. CSV + XLSX only.
SUPPORTED_EXPORT_FORMATSinapps/web/lib/services/payment-report-export.service.tsis the single place to extend, and?format=pdfalready returns a 400 naming the supported formats rather than failing obscurely. Revisit if an operator asks for a print-ready statement rather than a data extract.
Q-053b Should enabling email 2FA require a verified email address?
- Context. Spec 047 lets any credentials account switch on email 2FA. If
the address on file is wrong or unreachable, the member locks themselves
out at their next sign-in. The obvious guard — refuse to enable until
client_profiles.email_verifiedistrue— is unusable today because that column defaults tofalseand the sign-up flow never flips it, so the guard would refuse essentially every account on a default deployment. - Options.
- Allow, and guard only the unrecoverable case (current). Enabling is
refused with
503EMAIL_NOT_CONFIGUREDwhen the deployment has no mail provider at all — the case where a code could never arrive — and the operator unlock is documented in Email Two-Factor Authentication. - Require
email_verified, and make the sign-up flow actually set it. Correct, but it is a change to registration and to every existing row, which belongs in its own spec. - Confirm the factor at enable time: send a code and require it before the switch sticks. Proves deliverability without touching registration, at the cost of an extra route and an extra UI step.
- Allow, and guard only the unrecoverable case (current). Enabling is
refused with
- Default. Allow, guarding only the unrecoverable case. Revisit together with any work that makes email verification mandatory at sign-up; the enable-time confirmation is the natural follow-up if lockouts show up in the field.
- Owner. Template maintainers.
- Status.
open.
Q-053c How should a session-free /api route resolve the tenant on a host-routed multi-tenant deployment?
- Context.
POST /api/auth/2fa/resendruns mid-login, so it has no session, and Next middleware does not run for/api— thex-tenant-domainheader the middleware injects is therefore absent andgetTenantId()falls through toTENANT_ID/ the default tenant. On a deployment that routes tenants by host, a resend for a member outside that tenant finds no account and (correctly, per the enumeration-safe envelope) reports nothing. This is a property of every session-free/apiroute in the repo, not of this one; the primary issuing path, the sign-in server action, runs behind middleware and is tenant-correct. - Options.
- Leave it (current). The sign-in flow always works; only the "send a new code" convenience is affected, and only on host-routed multi-tenant deployments.
- Resolve the tenant from the request's own
Hostheader inside the route and thread it into the account / profile lookups. Needs a tenant argument ongetClientAccountByEmail,verifyClientPasswordandgetClientProfileByUserId, which every other caller shares. - Make the resend button re-submit the sign-in server action without a code, so the issuing path is always the tenant-correct one, and keep the route for programmatic callers.
- Default.
Leave it— resolved at the resolver, which turned out to be a fourth option none of the three above described:getTenantId()'s header step now falls back to the request's ownHostwhenx-tenant-domainis absent. That is the same valueproxy.tscopies into the header, so it trusts nothing new; it changes no signature, so the shared helpers keep their callers; and it fixes every session-free/apiroute at once, including a POST straight to/api/auth/callback/credentials. It can only ever select an existingtenantrow, so an unrecognised host falls through toTENANT_ID/ the default tenant exactly as before. - Owner. Template maintainers.
- Status.
resolved(PR #1048).
How to add a question
Q-047a Should the internal mirror segment stay reachable as a public URL?
- Context. The
.mdmirrors are served by route handlers that used to live in_-prefixed private folders, which the App Router excludes from routing — so every mirror URL 404'd. Renaming the segment (_md→md,_static-md→static-md) is what makes them routable at all, and it also makes the internal paths directly requestable: measured/items/<slug>/mdand/static-md/about→200 text/markdown, and/en/items/<slug>/md→307to the unprefixed form (localePrefix: 'as-needed'). - Options.
- Leave them reachable. The handlers already send
X-Robots-Tag: noindex, they are absent from the sitemap and nothing links to them, so the exposure is a duplicate of content already public at the.mdURL. - Add a
Disallow: /*/md$+/*/static-md/pair torobots.ts, or gate the handlers on an internal header set by the rewrite.
- Leave them reachable. The handlers already send
- Default. Leave them reachable.
noindexalready answers the only concern (crawlers indexing the mirror instead of the canonical HTML), and a header gate would make the handlers untestable except through the rewrite. - Owner. Template maintainers.
- Status.
open.
Q-047b Should the doubled origin in the item / CMS-page text/markdown alternates be fixed here?
- Context.
getLocalizedUrl()already returns an absolute URL, so`${appUrl}${getLocalizedUrl(…)}.md`emitshttp://hosthttp://host/…md. PR #1046 fixes the four static info pages (/about,/cookies,/privacy-policy,/terms-of-service); the same doubling remains onapp/[locale]/items/[slug]/page.tsxandapp/[locale]/pages/[slug]/page.tsx. - Options.
- Leave to the owning PR / a follow-up. Spec 047 is about reachability; touching the same four-file blast radius as PR #1046 while it is open invites a conflict, and the two remaining pages are the same one-line change.
- Fix all six in this PR.
- Default. Leave to a follow-up, tracked here. Spec 047's e2e guard
deliberately checks alternate-href resolution on
/helpand/pricingonly — the two pages whose href is already origin-correct — so it neither duplicates nor collides withmd-alternate-link-absolute-url.spec.ts. - Outcome. Answered by spec 048
(PR #1045), which fixed the doubling on
about,cookies,items/[slug]andpages/[slug]alongside the two legal routes. Nothing is left for this spec to do here. - Owner. Template maintainers.
- Status.
answered.
Spec 048 — Legal page SEO metadata from Markdown frontmatter
Q-048a Which routes adopt buildStaticPageMetadata()
- Context.
apps/web/lib/seo/static-page-metadata.tsresolves a static info page's<title>/<meta name="description">from the data repository's Markdown frontmatter with an i18n fallback. EW-17 scoped the change to/terms-of-serviceand/privacy-policy, but/aboutand/cookiesare the same shape (dedicated route,pages/<slug>.<locale>.mdbody, frontmatter title in the<h1>and in the.mdmirror, i18n-onlygenerateMetadata) and have the same drift. - Options.
- Legal routes now,
about/cookiesin a follow-up. Keeps the ticket's diff reviewable; the helper is already generic, so the follow-up is a four-line change per route. - Migrate all four in one PR. Removes the inconsistency immediately but
widens a small ticket into every static info page, and
/aboutis asserted by more specs (about.spec.ts,each-page-document-title-length.spec.ts).
- Legal routes now,
- Default. Legal routes now,
about/cookiesin a follow-up. - Owner. Template maintainers.
- Status.
open.
Q-048b .md vs .mdx for data-repository pages
- Context. EW-17 asks for
terms-of-service.mdx/privacy-policy.mdx. The template's reader (fetchPageContent) globspages/<slug>.<locale>.mdand the content is rendered bynext-mdx-remote, so the files already get full MDX rendering under a.mdextension; the demo data repository ships them as.md. - Options.
- Keep
.md. No behaviour difference, no data-repository migration, and the extension every existing Work and thepages/docs already use. - Accept both extensions in
fetchPageContentand document.mdxas preferred. Matches the ticket's wording; costs an extra stat per lookup and a docs/data migration for no rendering gain.
- Keep
- Default. Keep
.md— the requirement is "MDX rendering with frontmatter", which is satisfied.
Spec 049 — Visitor-facing FAQ page
Q-049a Should the FAQ render as an accordion rather than plain prose?
- Context. Spec 046 renders
pages/faq.<locale>.mdthrough the sharedMDXcomponent, exactly like/about,/cookiesand the legal pages, so a long FAQ is a long scrolling page. Directory Kit (the reference the Jira ticket names) uses collapsible question rows. An accordion would shorten the page and make scanning easier, but it needs a client component, and the answers must stay in the server-rendered HTML for theFAQPagerich result and the.mdmirror to keep working. - Options.
- Plain prose (current). Zero client JS, identical to every other static info page, and the operator controls the look through the data repo's Markdown. Content and structured data come from exactly one source.
- A client accordion that hydrates over the same server-rendered headings
and answers (
<details>/<summary>would even do it without JS). Nicer for long FAQs; a second rendering path to keep consistent with the parser's heading rules, and a first client component on an otherwise static page.
- Default. Plain prose. Revisit if a Work ships an FAQ long enough that
scanning it becomes the complaint.
<details>-based progressive enhancement is the cheapest upgrade path and would not break the parser or the mirror.
How to add a question
- Pick the next available
Q-NNN…id under the relevant spec. - Use the format above.
- Always include a Default; never block on a question.
- Append a line to
log.md:YYYY-MM-DD questions: added Q-NNN — short summary.