Documentation & Specs Change Log
2026-09-04
spec-050: added the blog reader surface for generated directory sites —/bloglisting with configurable pagination and search,/blog/[slug]post pages, category and tag archives,/blog/rss.xml, sitemap entries and 21-locale strings, all reading.content/posts/through the existinglib/content.tspipeline (spec 050, EW-25..EW-29).spec-048apps/web/lib/seo/{frontmatter,static-page-metadata}.tsapps/web/app/[locale]/{terms-of-service,privacy-policy}: the two legal routes now build their SEO metadata from the data repository’s Markdown frontmatter (title/description) through the newbuildStaticPageMetadata()helper, with the i18n strings kept as the fallback; both routes gain aloading.tsx; the<h1>, the "last updated" chip andrenderStaticPageMarkdown()now share one non-empty-string frontmatter reader with the<head>; the doubled base URL in thetext/markdownalternate is fixed here and inabout,cookies,items/[slug]andpages/[slug]; and the data-repository file layout is documented in the newdocs/guides/static-page-content.md(spec 048, EW-17, PR #1045).spec-047: the per-page.mdMarkdown mirrors were dead on every URL they advertise — the seven route handlers lived in_-prefixed folders, which the App Router drops from the route table, so thenext.config.tsrewrite destinations did not exist, and the unprefixed sources additionally pointed at destinations missing the[locale]segmentproxy.tsrefuses to add to dotted paths. Handlers renamed (_md→md,_static-md→static-md; public URLs unchanged), unprefixed rewrites given an explicit default-locale destination via a new dependency-freeapps/web/lib/i18n/locales.tsthatlib/constants.tsre-exports, and unknown category/tag slugs now 404 like their HTML pages (spec 047, PR #1050).apps/web-e2e:md-mirror-routes.spec.tsrewritten fromstatus < 500(which a 404 satisfied, which is why the breakage above shipped and stayed) to the real contract — exactly 200,text/markdown,X-Robots-Tag: noindex, a body that names the canonical page it mirrors — across the static info pages, a discovered item / category / tag, a non-default locale, the unknown-slug 404s, and the advertised alternate href actually resolving (PR #1050).spec-047: the category and tag mirrors now also readsettings.categories_enabled/settings.tags_enabledfrom.works/works.yml. A site that switches a facet off getsnotFound()on the HTML listing but was still served the full listing at<path>.md— measured404 text/htmlfor/categories/<id>against200 text/markdownfor/categories/<id>.md— so the mirror published a surface the site had withdrawn (PR #1050).questions: added Q-047a (internal/mdsegment left publicly reachable behindnoindex) and Q-047b (doubled origin still present on the item and CMS pagetext/markdownalternates) (PR #1050).spec-047: renumbered fromspec-046after PR #1043 merged todevelopunder that number; theQ-046a/Q-046bids it defines are the pricing spec's, and this spec's are nowQ-047a/Q-047b(PR #1050).apps/web-e2e: addedpublic/md-alternate-link-absolute-url.spec.ts, the regression guard for the doubled-origintext/markdownalternate fixed in PR #1045. It reads the href the HTML actually advertises —md-mirror-routes.spec.tsfetches the.mdpaths directly and never looks at it — and asserts each page declares exactly one alternate, and that every matched href is a single absolute URL whose pathname is the plain<page>.mdmirror path. The malformed form survived bothnew URL()and a "one://" check because Next.js resolved the unparseable doubled string againstmetadataBase, burying the origin in the pathname — observed on a dev server ashref="http://localhost:3000/http:/localhost:3000http:/localhost:3000/about.md"— so the pathname comparison is the load-bearing assertion. Covers the six static info pages,/pages/<slug>and a runtime-discovered item detail page, across the default and/frlocale prefixes (PR #1046).spec-053: email two-factor authentication for client accounts — enable/disable card on/client/settings/security, hashed six-digit code emailed on every credentials sign-in, 10-minute expiry with resend, 5-failure / 15-minute database-tracked lockout, and OAuth-only accounts refused in the UI and at the API (spec 053, plan, tasks, Jira EW-135 … EW-142, PR #1048).docs/authentication: added Email Two-Factor Authentication covering the member flow, the threeTWO_FACTOR_*env vars, the operator unlock procedure, and the hash-only storage contract (PR #1048).questions: added Q-047a — should adminusersrows get email 2FA too? Default: no, client profiles only. Added Q-047b — should enabling 2FA require a verified email address? Default: allow, and guard only the unrecoverable no-mail-provider case. Added Q-047c — how should a session-free/apiroute resolve the tenant on a host-routed multi-tenant deployment? Resolved:getTenantId()falls back to the request's ownHostwhen the proxy-injectedx-tenant-domainis absent, which is what happens on every/apiroute (PR #1048).
2026-09-03
spec-049: added the visitor-facing FAQ page at/faq— content from the data repository (pages/faq.<locale>.md) with a built-in fallback FAQ,FAQPageJSON-LD generated from that content, footer + More-menu entries, sitemap / robots /llms.txt//faq.mdmirror wiring, i18n keys in all 21 locales, and Playwright coverage (spec 049, EW-47, PR #1044).docs/features: addedfaq-page.md(content contract, question detection, defaults and discovery) and indexed it in the docs sidebar (spec 049, PR #1044).docs/features:seo.mddocumented aFAQPagegenerator that did not exist;lib/seo/schema.tsnow has one, and the page describes how the content contract drives it (spec 049, PR #1044).questions: added Q-049a — should the FAQ render as an accordion rather than plain prose (spec 049, PR #1044).spec-046: review follow-ups — the Markdown-to-text reduction that feeds the schema now keeps literal*and_(a page renderingsnake_casewas published assnakecase), removes raw HTML with a scanner run to a fixpoint instead of oneString.replacepass (CodeQLjs/incomplete-multi-character-sanitization), andrenderStaticPageMarkdownfalls back on an empty body the way the HTML pages already did, so no static page and its.mdmirror can disagree. Addedapps/web/lib/seo/__tests__/faq-parser.spec.ts(spec 049, PR #1044).spec-046: further review follow-ups on the same reduction — code spans and fenced blocks are now lifted out before any other rule and restored last (a page rendering`_setup_`was marked up as_setup_losing its underscores),*may open and close inside a word as CommonMark specifies while_may not, and the emphasis rules run to a fixpoint so nested spans such as**bold *nested* text**no longer leave their outer delimiters in the schema (spec 049, PR #1044).spec-046: third review round —/faqand the/faq.mdmirror it advertises now share one emptiness rule (resolveStaticPageBody). Afaq.<locale>.mdwhose frontmatter is followed by a blank line loads ascontent: '\n\n', which is truthy, so the page rendered an empty body — losing itsFAQPagerich result — while the mirror served the built-in FAQ./about,/cookies,/privacy-policyand/terms-of-serviceresolve through the same helper. Addsapps/web/lib/seo/__tests__/static-page-body.spec.tsand an e2e cross-check that every question/faqmarks up appears in/faq.md(spec 049, PR #1044).spec-046: EW-131 — the optionalpricing:block of.works/works.ymlis now documented field by field and validated on read: newdocs/configuration/works-yml-pricing.md+ completedocs/configuration/examples/works-pricing.example.yml, newapps/web/lib/config/schemas/works-pricing.schema.tscalled fromgetConfig(),provideracceptsstripe/lemonsqueezy/polar/solidgate/manualandPROaliasesSTANDARD; a malformed block is logged per field and falls back to the built-in plans instead of throwing.provider: manualis carried through provider resolution rather than erased, so a site that declares it never starts an in-site checkout — distinct from omittingprovider, which keeps the Stripe default (spec 046, PR #1043).docs/payment:payment.md"Configure Pricing Plans" andconfiguration/payment-config.mdnow point at the fullworks.ymlpricing reference and documentprovider: manual+ thePROalias (spec 046, PR #1043).questions: added Q-046a (shouldprovider: manualrender its own pricing surface?) and Q-046b (should a malformedpricing:block ever be fatal?), both with chosen defaults (spec 046, PR #1043).spec-051: admin Billing Issues queue at/admin/billing-issues— payment problems derived from the payment records the site already stores (failed charges, disputed/refund cases, subscriptions stuck pending or expired-while-renewing), with mark-resolved/dismissed and a refund issued through the provider named on the underlying subscription. Adds thebilling_issuestriage table (migration0040) and wires the previously caller-lessPaymentProviderInterface.refundPaymentseam; money state stays onsubscriptions(spec 051, Jira EW-116, PR #1049).spec-052: admin Payment Reports at/admin/payment-reports— the stored payment records filtered by date range, plan, provider and status, with roll-ups by currency/plan/provider/status and CSV + XLSX export sharing one filter validator with the JSON view. PDF deliberately not shipped; see Q-052-1 (spec 052, Jira EW-117, PR #1049).spec-051/spec-052review follow-ups (same PR): unit boundaries made explicit and per-currency (subscriptions.amount*are MAJOR units,billing_issues.amountis minor, provider adapters take major — see the table in spec 051 §9); refunds are claimed atomically viabilling_issues.refund_claimed_atbefore any provider call; report roll-ups are grouped by currency; revenue no longer falls back fromamount_paid = 0to the scheduled amount; an over-cap export is refused rather than truncated; date filters reject calendar-invalid values such as2026-02-30.spec-051/spec-052review round 3 (same PR): both writing POST routes test the RAW body for emptiness instead of a trimmed copy — a whitespace-only payload was reading as "no body supplied", which on.../refundmeant a full irreversible refund; and/api/admin/payment-reportsnow applies the same strict whole-integer pagination pre-check the billing-issues list uses, solimit=3.5is a 400 rather than a 200 carrying a page size nobody asked for (spec 051 §9, spec 052 §9, PR #1049).spec-051/spec-052review round 4 (same PR):POST .../refundnow treats ONLY an absentamountkey as "refund the whole charge" —{"amount": null}and{"amount": ""}, the shapes a truncated payload arrives in, were skipping validation and issuing a FULL refund, andNumber()coercion was turningtrueinto a 1-unit partial refund; the failed-payment webhook can now adopt a payment intent onto an issue whose stored reference is NULL (ne(col, x)is never true against NULL in SQL, so exactly the issues with no refund target could never gain one); the report export reads one snapshot that is both the file's rows and the input to its summary, so a concurrent payment can no longer truncate the file while the summary counts rows it does not contain; the refund dialog rejects sub-unit precision instead of rounding the typed amount; and the billing-issues queue renders a load failure instead of "No billing issues" (spec 051 §9, spec 052 §9, PR #1049).questions: added Q-051-1 — should a refund carry a provider-side idempotency key? Default: no, fix it in the payment-provider spec where the adapter interface lives.questions: added Q-052-1 — should the payment report also export PDF? Default: CSV + XLSX only, no new dependency.spec-051/spec-052renumbered from 046/047 (same PR): PR #1043 mergedspec-046(works-yml-pricing-config) intodevelopfirst, so these two took the next numbers no other open PR claims. Directory names, index rows,docs/log.mdanddocs/questions.mdids (Q-051-1, Q-052-1) and every in-codeSpec 04xcomment move together; no behaviour changes.spec-052: the export's revenue roll-up moved toapps/web/lib/db/queries/payment-report-summary.ts— a module with no runtime imports, so it can be unit tested (pnpm --filter @ever-works/web test:unit).payment-report.queries.tsre-exports the two types and the function, so no import path changes. The new spec pins the coalesce semantics the SQL was carrying: a COLLECTED amount of 0 stays 0 rather than falling back to the scheduled amount, and currency is part of every grouping key.
2026-08-25
spec-045: documented and hardened the shared handler/POST /api/stripe/platform-webhookpath, including HMAC fail-closed coverage, formatted payment amounts, and retry-safe event coordination (spec 045, PR #1037).
A running log of meaningful changes to documentation, specs, and the project's living-document set (constitution, agent rules, plans). One line per change, newest at the top. Every line follows the form:
YYYY-MM-DD area: short summary
Where area is one of:
docs/<section>— a docs page.spec-NNN— a feature spec underdocs/spec/NNN-…/.constitution— an amendment to.specify/memory/constitution.md.agents—AGENTS.mdchange.claude—CLAUDE.mdchange.index—docs/index.mdchange.questions—docs/questions.mdchange.
This file lives in the docs site and acts as a hand-maintained companion to git history. Use this when reading what changed and why at a higher level than per-commit diffs.
2026-08-23 — Chore: force LF for container scripts (.gitattributes)
- infra:
docker-entrypoint.sh,*.shand the Dockerfiles are nowtext eol=lfin.gitattributes. A Windows checkout (core.autocrlf=true) produced a#!/bin/sh\rshebang and the built site image died withexec /usr/local/bin/docker-entrypoint.sh: no such file or directory(2026-08-23, local image build while the CI runner pool was stalled). No runtime change for CI-built images. (PR: pending) - infra:
docker-entrypoint.sh,*.shand the Dockerfiles are nowtext eol=lfin.gitattributes. A Windows checkout (core.autocrlf=true) produced#!/bin/sh\r(a trailing carriage return, written here as an escape rather than as a literal CR — the literal is what a Windows checkout kept rewriting) and the built site image died withexec /usr/local/bin/docker-entrypoint.sh: no such file or directory(2026-08-23, local image build while the CI runner pool was stalled). No runtime change for CI-built images. (PR: pending)
2026-08-22
- spec-042: site identity metadata —
<title>/ meta description /og:site_name/ WebSite JSON-LD / OG images now resolve from the Work's.works/works.yml(company_name,name,settings.homepage.hero_*) vialib/seo/site-identity.tswhenNEXT_PUBLIC_SITE_*are unset (spec, #1019) - spec-043:
/docsAPI reference embed fixed — route-scopedX-Frame-Options: SAMEORIGIN+ CSP (frame-ancestors 'self',cdn.jsdelivr.net) for/api/referenceinnext.config.ts; e2e asserts the headers and that the iframe document mounts (spec)
2026-08-22 — Feat: public payment config served at runtime (spec 044)
- spec-044: platform-deployed k8s Works are built once by
k8s-build.ymlwith no per-Work env, soNEXT_PUBLIC_*is never inlined into the browser bundle. Client code that readprocess.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY/NEXT_PUBLIC_STRIPE_DYNAMIC_PRICING/NEXT_PUBLIC_DEMOdirectly (LayoutThemeContext.getConfiguredProviders,stripe-payment-modal,add-payment-method-modal,use-stripe-products,use-payment-availability) therefore sawundefinedeven when the server hadSTRIPE_SECRET_KEYet al. — every platform-deployed/pricingrendered only the FREE card plus a "Payment failed: Payment system is not configured" toast on load. (spec) - New
GET /api/payment/public-config(force-dynamic,Cache-Control: no-store) returns{ stripePublishableKey, dynamicPricing, demo, configuredProviders }from the server's runtime env — public values only, never secrets. Shared pure readerapps/web/lib/payment/public-config.ts; new React Query hookapps/web/hooks/use-public-payment-config.ts(initialDatafrom build-timeprocess.env, 5-min stale, works without aQueryClientProviderby falling back to the shared browser client) layers runtime values over the inlined ones, so Vercel/demo builds keep first-paint behaviour and an unavailable route degrades to pre-044. - Consumers switched:
LayoutThemeContext(configuredProviders),PaymentFormModal(key from hook; the "not configured" error effect now only fires whileisOpenand after the fetch settled — a closed modal never toasts),AddPaymentMethodModal(loadStripefrom the runtime key instead of a module-levelloadStripe(process.env…!)),useStripeProducts/useDynamicPricingStatus/usePricingSection(useStripeDynamicPricingEnabled()),usePaymentAvailability(demofrom hook; SSR default kept until the first fetch settles). e2e:apps/web-e2e/tests/api/payment-public-config.spec.ts. Docs:docs/payment/stripe.mdruntime-key tip,.env.examplenote. PR #1023. - questions: added Q-044a — should
[locale]/layout.tsxalso seed the public payment config as a server prop (zero-request first paint)? Default: route + hook only.
2026-06-17 — Feat: deploy_k8s.yaml multi-host Ingress (K8S_EXTRA_HOSTS)
- spec-041: EW-741 (ever-works PR #1322) made
DeployServicepush a newK8S_EXTRA_HOSTSrepo secret on every k8s deploy — comma-separated, lowercased, deduped list ofWorkCustomDomainrows for the Work — butdeploy_k8s.yamlwas still rendering a singlespec.rulesentry +spec.tlsblock for the primaryK8S_INGRESS_HOST. Adding a custom domain in the Deploy tab therefore had no observable effect after redeploy; the cluster Ingress only knew the managed*.ever.workssubdomain. - "Render and apply manifests" step now builds a
HOSTS=()array (primary first, then splitK8S_EXTRA_HOSTSby,, lowercased + deduped by aseenset), loops the array when emittingspec.rules, and loops it again when emittingspec.tlsso cert-manager issues a separate cert per host. Per-host TLSsecretNameuses the sametr -c 'a-z0-9' '-' | sed …collapse the original single-host path used, so the managed*.ever.workshost's existing TLS secret keeps its name and cert-manager does not re-issue. - "Deployment summary" step now also echoes one
Also serving https://…line per extra host so multi-host deploys are discoverable in the Actions run log. - Backwards-compatible: when
K8S_EXTRA_HOSTSis empty / unset the rendered Ingress is byte-identical to pre-EW-741 output.
2026-06-17 — Feat: instant sparkline bump + flicker-free rollback on upvote
- spec-037: the item-detail Statistics card already tracked total votes
optimistically via
useItemVote'ssetQueryData(['item-votes', …]), but the sparkline (today's bar) and the activity totals lagged a full network round-trip behind the vote button — and a failed mutation triggered a cache refetch instead of a snapshot rollback, producing a brief flicker. - Exported
ITEM_ACTIVITY_QUERY_KEY+ItemActivityPayloadfromapps/web/components/item-detail/item-stats-section.tsxso mutators can surgically patch the activity cache without restating the cache key. apps/web/hooks/use-item-vote.tsnowcancelQueriesfor both['item-votes', id]and[ITEM_ACTIVITY_QUERY_KEY, id]before snapshotting, derives the signed delta insidesetQueryData(old)so the count + userVote + activity bump stay consistent under concurrent refetches, applies that same delta to the activity totals + today's sparkline point on the same frame as the vote, and on error restores the snapshot viasetQueryData(key, value)to avoid the refetch flicker.onSuccessstill invalidates the activity query so the next render reconciles with the authoritative server count.- Credits @joel-kalema (rebased from #945 onto fresh develop). PR #961.
2026-06-16 — Fix: CI-safe git-CMS writes + favorite-toggle e2e race
- spec-039: the e2e suite's authenticated write-flow specs (admin create
collection, client submit item, favorite toggle) timed out at ~30s in CI.
Two causes: (1)
CollectionGitService/ItemGitServicepullon init andpushafter each write hit the unreachable CI content remote with no HTTP timeout, blocking the POST past the redirect/modal wait — addedisContentGitRemoteDisabled()(apps/web/lib/services/content-git-offline.ts, gated onCI/CONTENT_GIT_OFFLINE) and guardedsyncWithRemote+pushin both services; runtime (noCI) still pushes. Also added an in-flight git-service init lock toitem.repository.ts/collection.repository.ts(parallel CI workers no longer init isomorphic-git on the same.gitat once). (2) Favorites are DB-backed; an early click beforeuseCurrentUserresolved opened the login modal whose backdrop then ate clicks — hardenedclickFavorite()inapps/web-e2e/page-objects/public/item-detail.page.tsto dismiss the modal and only count a click that flips the label. No prod behaviour change; no tests skipped. Seedocs/spec/039-e2e-git-cms-ci-safe/spec.md.
2026-06-16 — Feat: k8s deploy provisions Work runtime env
- spec-040: k8s-deployed directory sites 500 at first render (
[auth] AUTH_SECRET must be set in production) because the Deployment only carried NODE_ENV/PORT/HOSTNAME. Added adeploy_k8s.yamlstep that materializes a${WORK_SLUG}-runtime-envSecret from the AUTH_SECRET/COOKIE_SECRET/COOKIE_SECURE/DATABASE_URL secrets the platform pushes (+ NEXT_PUBLIC_APP_URL/COOKIE_DOMAIN from the ingress host), anddeployment.yamlmounts it viaenvFrom(optional). Platform half: ever-works DeployService.ensureRuntimeEnv + WorkRuntimeEnvService. Seedocs/spec/040-k8s-deploy-runtime-env/spec.md. (PR: pending)
2026-06-15 — Fix: k8s deploy probe timeouts (startup/readiness/liveness)
- spec-038: the k8s deploy manifest template
.deploy/k8s-platform/deployment.yamldefinedreadinessProbe/livenessProbeon/with notimeoutSeconds, so both inherited Kubernetes' 1-second default. A server-rendered/on a small shared node routinely exceeds 1s, so the liveness probe failed its default 3 attempts and kubelet killed the container in a permanent restart loop (observed: 460+ restarts,Exit Code: 143, on the first k8s-deployed Workawesome-compliance-automation-website). Added an explicitstartupProbe(≈5 min budget for first response before liveness/readiness apply) and settimeoutSeconds: 5plusfailureThresholdon readiness (3) and liveness (6). No app code or image change; affects only deployed Works' pod health gating. Propagation to existing per-repoawesome-*copies is tracked in the Vercel→k8s migration runbook. Seedocs/spec/038-k8s-deploy-probes/spec.md. (PR: pending)
2026-06-13 — Docs: Neon database integration in the Vercel deploy guide
- docs/deployment: documented setting up the database via the Neon Vercel
Marketplace integration in
deployment/vercel.md, with the recommended integration settings (env var prefixDATABASE, Production branch on, Preview branch off) and a warning that per-preview database branches can balloon into hundreds of branches and a large bill.
2026-05-27 — Perf: item-detail first paint — stream similar-items, persist + parallelize
- spec-037: fixed blank/slow first paint on
/[locale]/items/[slug]. The page used toawaitthe similar-items computation (a full-catalogue scan viafetchItems) before returning any HTML, blocking first paint on a below-the-fold carousel. Now the carousel is streamed behind its own<Suspense>boundary incomponents/item-detail/item-detail.tsx(React 19use()+ a server-createdsimilarItemsPromisepassed down from the page), so the hero/content/sidebar paint immediately and the rail arrives a beat later with a skeleton. Also addedgetCachedSimilarItemsinapps/web/lib/content.ts(wrapsfetchSimilarItemsinunstable_cache, keyed by slug + locale + maxResults, pinned to the content revision, taggedcontent/items/item:<slug>) so the scored list survives serverless cold starts / is shared across instances, and parallelized the page's item + translations loads (Promise.all). Final markup and item ordering unchanged. Seedocs/spec/037-item-detail-perf/spec.md. (PR: draft)
2026-05-27 — Fix: Edge Runtime build break in activity-feed push client
- spec-024: replaced the
import { randomUUID } from 'node:crypto'at the top ofapps/web/lib/services/platform-activity-feed/push-client.tswithcrypto.randomUUID()(Web Crypto API). The module is transitively imported from edge-compatible bundles (instrumentation.ts, the NextAuth adapter inlib/auth/index.ts, andlib/content-config-file.ts→/api/admin/navigation), so Turbopack rejected thenode:*import with "A Node.js module is loaded ('node:crypto') which is not supported in the Edge Runtime" and the resultingEcmascript file had an errorblocked the[locale]/submitpage from rendering — which in turn failed theclient can submit a new item via the submit formPlaywright test (button never appeared, hencemissing-required-fields="<unreadable>"). Spec 024's plan.md:90 already prescribedcrypto.randomUUID(); the original import was a deviation. Web Crypto'srandomUUIDis available in Node 20+ (this repo's minimum) and the Edge Runtime, so no behavior change beyond removing the build-time error.
2026-05-27 — Spec 036: Docker build and publish workflow
- spec-036: added
.github/workflows/docker-build-publish-dev.yml,.github/workflows/docker-build-publish-stage.yml, and.github/workflows/docker-build-publish-prod.yml, GHCR-first Docker build/publish workflows for the template's rootDockerfile. The Dockerfile now mirrors the sibling../ever-worksmonorepo image shape withturbo prune @ever-works/web --docker, pruned install/build stages, and a standalone Next.js runtime image;turbo.jsonnow allowsSTANDALONE_BUILDthrough to the Next.js build so.next/standaloneis emitted. The workflows mirror the sibling../ever-worksbranch-specific registry pattern while adapting it to the template's single web image:developpublishesdirectory-web-template-dev,stagepublishesdirectory-web-template-stage, andmainpublishesdirectory-web-template, each taggedlatestand short SHA. Docker Hub and DigitalOcean Container Registry pushes are included only when their credentials are configured. Documented the workflows indocs/deployment/docker.md.
2026-05-26 — Spec 035: Admin header profile link
- spec-035: drafted
docs/spec/035-admin-header-profile-link/spec.mdand shipped the fix in the same PR (EWW-5). The admin branch of the sharedProfileButtondropdown (apps/web/components/profile-button/menu-items.tsx) omitted the "Your Profile" link the non-admin branch has, so an admin had no link to their own profile; added it, reusing the existingcommon.YOUR_PROFILE/common.YOUR_PROFILE_DESCi18n keys. The/adminpanel header (apps/web/app/[locale]/admin/layout-client.tsx) keeps itsAdmin Panellabel unchanged (an earlier revision added a header button, then reverted it per review feedback). (Spec was originally numbered 034 in the PR branch but renumbered to 035 on merge to avoid collision with the already- merged spec-034 Client Billing UI consistency.)
2026-05-26 — Spec 034: Client Billing — wire placeholder actions
- spec-034: connected previously-dead billing controls to real behaviour
(EW-649). Export / Export Results → client-side CSV via new
lib/utils/billing-csv.ts; View History → Payment History tab; status filter checkboxes lifted to the page and actually applied; Date Range → opens advanced filters; payment-card Download →invoiceUrl; LemonSqueezy Cancel Plan →POST /api/lemonsqueezy/cancelvia existinguseSubscriptionActions(confirm + toast + refresh; removedconsole.log- orphan modal state); Modify Plan →
/pricing; View Details → details disclosure; Contact Support →mailto:. No new API routes.
- orphan modal state); Modify Plan →
2026-05-26 — Spec 034: Client Billing page UI consistency
- spec-034: drafted
docs/spec/034-client-billing-ui-consistency/spec.mdand shipped the implementation in the same PR (EW-649). UI-only realignment of/client/settings/profile/billing(and itscomponents/settings/billing/**sub-components) with the client dashboard design system: neutral palette (dropped allslate-*andtheme-primary-*),bg-white dark:bg-white/3card surfaces withborder-neutral-200 dark:border-white/8, monochrome icon tiles,neutral-900 / whiteprimary CTAs. Rewrote the KPI cards (billing-stats.tsx) to mirror the dashboardStatsCard(no gradients) and the tab bar (tab-navigation.tsx) to the dashboard underline tabs; matched the page header toDashboardHeader. Routed page-level hardcoded strings through thebillingi18n namespace (new keysFREE,UPGRADE,RENEWS_ON,UPGRADE_UNLOCK_FEATURES,DAYS_LEFT,DAYS_TOTALinmessages/en.json; non-English locales fall back via the existingdeepmergeconfig). No functional/data changes.
2026-05-25 — Spec 033: Client profile Security & Billing blocks
- spec-033: drafted
docs/spec/033-client-profile-security-billing/spec.mdand shipped the implementation in the same PR (EW-648). Adds two owner-only read-only blocks to the bottom of the right column on/[locale]/client/profile/[username]: Security & Status (apps/web/components/profile/sections/security-status-section.tsx— email verification, two-factor, account status, member-since, link to/client/settings/security) and Billing & Plans (billing-plans-section.tsx— plan, account type, currency, links to/client/settings/profile/billing+/pricing, Upgrade CTA on the free plan). Both are pure async server components fed from data already loaded by the page; the account-private fields (status,plan,accountType,twoFactorEnabled,currency) thattoPublicClientProfilestrips are read from the unprojectedrawProfile, gated byeffectiveIsOwn(so they are hidden from visitors and in?preview=public). New English keys added under theprofilenamespace inmessages/en.jsonand translated into all 20 other locales (ar, bg, de, es, fr, he, hi, id, it, ja, ko, nl, pl, pt, ru, th, tr, uk, vi, zh); thedeepmerge(en, locale)fallback ini18n/request.tsremains as a safety net. No new queries, hooks, dependencies, or schema changes. (PR: #930, draft)
2026-05-24 — /submit: fix step-1 progress jumping to 100%
apps/web/components/directory/details-form/components/step-indicator.tsx.../validation/form-validators.ts: step 1's progress bar/checkmark jumped straight to 100% once Product Name + URL were filled, because the connector fill short-circuited to 100% on the navigation-gate fields (['name','link']). Reworked it to be proportional to the actually-tracked fields and reset step 1'sprogressFieldsto the five visible inputs (link,name,category,tags,description). Category/Tags are filtered out of the count when those features are disabled in settings, so the bar can still reach 100%. The step is now marked complete only when all applicable tracked fields are filled.
2026-05-24 — /submit: hide pricing-only promo sections in payment step
apps/web/components/pricing/pricing-section.tsx: gated the "Sponsor Ads" promo block, the "Enhanced Continue Section" (its own continue-to-/submit CTA), and the "Trust Section" behind!isReviewso they render only on the standalone/pricingpage, not whenPricingSectionis embedded as the submit-flow payment step (PaymentSteppassesisReview). Matches the existing!isReviewgating used for the section's decorative background and header; the submit flow has its own form navigation, so the continue CTA was redundant there.
2026-05-24 — i18n: translate remaining hard-coded strings on /submit
apps/web/components/submit/submit-form-client.tsx,apps/web/components/directory/details-form/steps/basic-info-step.tsx,.../components/step-indicator.tsx,.../details-form.tsx,.../validation/form-validators.ts: replaced the last hard-coded English strings on the submit page withnext-intlkeys — the four submit toast messages (invalid URL / success / failed / generic error), the Video URL field label, the video-preview iframe title, the rich-text editor placeholder (reused existingDETAILED_INTRODUCTION_PLACEHOLDER), and the three step-indicator/header titles (Basic Information / Payment / Review, via a newtitleKeyon eachStepDefinition). Added 9 newdirectory.DETAILS_FORMkeys (VIDEO_URL_LABEL,VIDEO_PREVIEW,STEP_TITLE_BASIC_INFO/PAYMENT/REVIEW,TOAST_INVALID_URL/SUBMIT_SUCCESS/SUBMIT_FAILED/SUBMIT_ERROR) across all 21 locale files. No behaviour change.
2026-05-24 — Fix: /submit drops location and extra categories
apps/web/components/submit/submit-form-client.tsx: the submit handler built its API payload from the singularcategoryfield (first selected id only) and omittedlocationentirely, so a multi-category selection silently lost every category after the first, and any location collected byLocationFields(including whenrequireLocationOnSubmitgates the form) was discarded before reachingPOST /api/client/items. The payload now sends the fullcategoriesarray when present and includeslocationwhen set — both are already supported byClientCreateItemRequestand persisted byClientItemRepository.createAsClient. UI-only fix; no API/schema changes.
2026-05-21 — Spec 032: Collection icon picker — implementation
- spec-032: drafted
docs/spec/032-collection-icon-picker/spec.mdand shipped the implementation in the same PR. Replaces the bare Icon (emoji or URL) input on the/admin/collectionsCreate / Edit modal with a co-locatedEmojiIconInput(apps/web/components/admin/collections/emoji-icon-input.tsx- curated
emoji-data.ts). Typing:opens a GitHub-/Discord-style suggestion popover with keyboard nav (arrows, Enter/Tab, Esc, Home/End), debounced-via-useDeferredValuesearch, exact-shortname auto-replace on trailing space, a recent-picks chip backed by a versioned-keylocalStoragestore (evw_admin_collections_recent_emojis_v1, 16-entry cap), and a 40×40 preview tile that renders the value as an emoji glyph or, forhttps://…//relative/data:image/…values, an<img>. Existing URL paste behaviour is unchanged and the underlyingicon_urlfield still stores a single Unicode glyph or a raw URL — no backend changes. Zero new dependencies: the curated dataset is ~300 entries inline. Jira: EW-646. PR: #920.
- curated
2026-05-20 — Spec 031: client Danger Zone (account deletion UI) — draft
- spec-031: drafted
docs/spec/031-client-danger-zone/spec.mdproposing a new red-accented Danger Zone section at the bottom of/client/settings, plus a dedicated/client/settings/danger-zonesub-page that exposes the existingdeleteAccountserver action (apps/web/app/[locale]/auth/actions.ts) via a password-confirmed modal. No backend changes — wires the already-shipped soft-delete + activity log flow into the UI for the first time. Tracked under EW-635. PR pending.
2026-05-20 — Profile visibility toggle (Upwork-style)
- New
client_profiles.profile_visibilitycolumn (public|private, defaultpublic). Migration0038_add_client_profile_visibility.sql, additive and idempotent. - New settings page at
/client/settings/profile/visibilitywith an Upwork-style toggle plus side-by-side "Public / Private" radio cards spelling out the trade-offs (directory listing, link visibility, follower/portfolio exposure). PATCH /api/user/profilenow acceptsprofileVisibility.- Public profile page
/client/profile/[username]shows a "this profile is private" placeholder to non-owners when visibility isprivate. - Owner-only "Preview public view" toggle on the profile page
(
?preview=public) renders the page exactly as a visitor sees it. - Privacy hardening on the public profile render:
- Stopped leaking email local-part as username/displayName fallback.
RecentActivitySection(comments, favourites, follow ledger) is now owner-only — matches LinkedIn/GitHub/Upwork.- Free-form
locationtext now respectslocationPrivacy('private' hides it from non-owners, same as lat/long). - New
toPublicClientProfile()projection inclient.queries.tsdropsemail,phone,notes,tags,tenantId,twoFactorEnabled, moderation flags, billing flags and raw geo from the page payload.
- Owners always see their own profile regardless of setting.
- Spec doc deferred per request — feature ships PR-only.
2026-05-20 — Spec 027 follow-up: page-based pagination on /client + /admin notifications (PR #852)
- spec-027:
/client/notificationsand/admin/notificationslong lists now ship withUniversalPagination(Page X of Y, prev/next) instead of cursor-based infinite scroll. - API:
GET /api/client/notificationsandGET /api/admin/notificationsswitched to offset/limit and return{notifications, total, page, totalPages, unreadCount}. Defaults: limit 25, max 100 (client) / 200 (admin). Unread count still uses base scope so the header pill is stable while filters narrow. - Hooks:
useNotificationsswapsuseInfiniteQuery→useQuerywithplaceholderData: prevfor snappy page hops.useAdminNotificationsnow accepts{page, limit}and exposestotalPages/total/page. - Cache: mark / bulk / SSE mutations rewritten to mutate the flat
NotificationListResponseshape instead ofInfiniteData<ListPages>.NotificationListdropped itsIntersectionObserver+ sentinel; the dropdown still asks for page 1, limit 15. - UX: changing tab or filters resets
pageto 1 on both surfaces.
2026-05-19 — Spec 030: /client/submissions UI redesign (UI-only, develop-only)
- spec-030: drafted
docs/spec/030-client-submissions-redesign/spec.mdproposing a UI-only redesign of the client submissions page — responsive table/cards layout, KPI stats cards, status segmented tabs + sort UX, polished empty / error / skeleton states. No backend or hook contract changes. Renumbered from 029 → 030 during conflict resolution to avoid collision with the merged spec029-client-settings-preferences-section. - docs/spec: indexed spec 030 in
docs/spec/README.md.
2026-05-19 — Spec 028 round 17: leading-slash + host header + preferences gate (develop-only)
Round 17 of the rolling e2e coverage buildout. 8 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
URL / host security:
public/listing-with-multiple-leading-slashes.spec.ts—//hostno off-site redirect.public/listing-with-fake-host-header.spec.ts— Host header spoof no echo.
HTML hygiene:
public/listing-href-not-empty-or-hash.spec.ts— fewhref=""/href="#".public/listing-html-no-script-no-src.spec.ts— no empty inline scripts.public/listing-no-deprecated-link-rel.spec.ts— advisory: deprecated rel.public/listing-no-broken-anchor-content.spec.ts— no{{...}}/${...}in anchors.
Pages:
public/client-settings-preferences-anonymous.spec.ts— Spec 029 preferences gate.
API:
api/admin-mixed-method-flood.spec.ts— verb flood × sponsor-ads/comments/etc.
Branch: feat/e2e-coverage-1779217016. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 16: stacked locale + BOM/control + manifest shape (develop-only)
Round 16 of the rolling e2e coverage buildout. 10 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
URL / encoding edges:
public/listing-with-multiple-locale-prefixes.spec.ts—/en/fr/aboutstacks.public/listing-bom-and-control-chars.spec.ts— BOM/null/DEL/control chars.
HTML hygiene:
public/listing-overall-page-error-state.spec.ts— no "undefined"/"NaN"/[object Object] visible.public/listing-no-deprecated-noscript-block.spec.ts— noscript < 5KB.public/listing-form-submit-without-fields.spec.ts— empty submit no crash.
Perf + budgets:
public/listing-network-resource-budgets.spec.ts— total JS bytes budget.
Pages:
public/admin-survey-deep-anonymous.spec.ts— admin survey edit/preview/responses + locale + RSC.
API rejection:
api/admin-collections-deeper.spec.ts— collections + items nested CRUD.
Icons / manifest:
public/link-favicon-and-apple-touch-icon-shape.spec.ts— icon hrefs well-formed.public/manifest-shape.spec.ts— manifest valid JSON with name.
Branch: feat/e2e-coverage-1779206154. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 15: URL malforms + theme/submissions trash + webhook edges (develop-only)
Round 15 of the rolling e2e coverage buildout. 10 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
URL malforms:
public/listing-with-double-question-mark.spec.ts—?a=b?c=dtolerance.public/listing-with-percent-encoded-slash.spec.ts—%2Fin segments.public/listing-with-multi-percent.spec.ts— double-encoded + emoji segments.public/paging-overflow-large-pages.spec.ts— page=10000 on listings.
Client settings deeper:
public/theme-colors-page-protected.spec.ts— theme-colors anon + RSC + locale.public/submissions-trash-protected.spec.ts— trash anon + RSC + locale.
API rejection:
api/admin-clients-search-shapes.spec.ts— advanced-search anonymous.api/webhook-content-type-deeper.spec.ts— webhooks wrong CT / multipart / empty.api/admin-export-deeper.spec.ts— items export format/limit variants anon.
HTML hygiene:
public/listing-no-trailing-comma-anchor.spec.ts— no<a href="/foo,">.
Branch: feat/e2e-coverage-1779202525. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 14: locale × RSC + perf/CLS + admin CRUD deeper (develop-only)
Round 14 of the rolling e2e coverage buildout. 14 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Locale × RSC bounce:
public/admin-i18n-rsc-bounce.spec.ts— locale × admin × _rsc anonymous.public/client-i18n-rsc-bounce.spec.ts— locale × client × _rsc anonymous.public/sponsor-checkout-rsc-bounce.spec.ts— /sponsor + _rsc tolerance.
Perf + CLS + a11y:
public/listing-aspect-ratio-images.spec.ts— img dimensions vs aspect-ratio.public/listing-no-cls-from-late-fonts.spec.ts— no font-display:block/auto.public/listing-no-dialog-open-without-trigger.spec.ts— no auto-open dialog.public/listing-no-fixed-position-blocking.spec.ts— no fullscreen z>1000 overlay.public/listing-no-form-without-action.spec.ts— forms wire submit.public/auth-pages-input-attributes.spec.ts— auth inputs email+password.
API + responses:
public/listing-response-status-text.spec.ts— 200 responses declare CT.api/auth-session-no-pii-leak.spec.ts— anon session no hashes/tokens.api/admin-comments-deeper.spec.ts— comments CRUD rejection.api/admin-featured-items-deeper.spec.ts— featured-items CRUD rejection.api/admin-companies-deeper.spec.ts— companies CRUD rejection.
Branch: feat/e2e-coverage-1779198954. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 13: sitemap variants + HTML hygiene + admin CORS (develop-only)
Round 13 of the rolling e2e coverage buildout. 13 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Sitemap / SEO:
public/sitemap-index-shape.spec.ts— nested sitemap URLs absolute non-5xx.public/sitemap-images-and-news.spec.ts— image/news/video sitemap variants.public/sitemap-images-and-news-extras.spec.ts— sitemap alias paths.public/listing-meta-canonical-not-trailing-slash.spec.ts— canonical host.
Error rendering:
public/error-status-content-types.spec.ts— 404 returns HTML + body.public/listing-redirect-status.spec.ts— redirect chains end non-5xx.
HTML hygiene:
public/listing-html-validity-essentials.spec.ts— no script src=undefined, no [object Object], few >null<.public/listing-css-no-display-none-on-h1.spec.ts— h1 attached to DOM.public/listing-no-render-blocking-js.spec.ts— head scripts async/defer.public/listing-button-keyboard-activation.spec.ts— Enter/Space no JS error.public/listing-favicon-ico-content-type.spec.ts— favicon image content-type.
Admin / API:
public/admin-api-non-2xx-on-options.spec.ts— admin OPTIONS no wildcard CORS.api/admin-twentycrm-deeper.spec.ts— twenty-crm config/test-connection rejection.
Branch: feat/e2e-coverage-1779188051. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 12: a11y deeper + HTML sanity (develop-only)
Round 12 of the rolling e2e coverage buildout. 16 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
a11y deeper:
public/listing-form-labels.spec.ts— auth inputs have label/aria/placeholder.public/listing-tabindex-non-negative.spec.ts— no positive tabindex.public/listing-aria-hidden-conflict.spec.ts— aria-hidden no focusable.public/listing-no-empty-anchors.spec.ts— anchors have accessible name.public/listing-color-contrast-advisory.spec.ts— body color != bg.public/listing-no-onclick-on-non-interactive.spec.ts— no div onclick.public/listing-buttons-have-types.spec.ts— buttons declare type.public/listing-input-autocomplete-shape.spec.ts— pw autocomplete shape.public/listing-document-language-set.spec.ts— html.lang non-empty.
HTML sanity:
public/listing-skeleton-renders.spec.ts— listing body has content <2s.public/listing-no-deprecated-html.spec.ts— no <font/center/marquee/etc.public/listing-multiple-h1.spec.ts— at most 3 h1 elements per page.public/listing-no-duplicate-ids.spec.ts— unique DOM ids.public/listing-css-no-blocking-fonts.spec.ts— no font-display:block.
API shape:
api/stripe-products-shape.spec.ts— products read + HEAD/OPTIONS.api/auth-callback-cookie-shape.spec.ts— wrong-csrf no session cookie.
Branch: feat/e2e-coverage-1779184408. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 11: locale prefix sweep + cookie/header hygiene (develop-only)
Round 11 of the rolling e2e coverage buildout. 17 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Locale prefix sweep:
public/auth-locale-prefix-tolerance.spec.ts— every locale × auth path.public/items-locale-prefix-tolerance.spec.ts— detail routes × locale.public/pricing-locale-prefix.spec.ts— //pricing + sponsor. public/static-info-locale-prefix.spec.ts— //about/help/etc. public/favorites-and-newsletter-locale.spec.ts— favorites/newsletter/map/submit.api/admin-i18n-locale-prefix.spec.ts— API endpoints with locale prefix.
SEO/cache headers:
public/hreflang-consistency.spec.ts— well-formed hreflang.public/stale-while-revalidate-shape.spec.ts— directive form parses.public/listing-resp-vary-header.spec.ts— Vary tokens valid.public/listing-prefetch-not-blocking.spec.ts— prefetch hrefs non-5xx.
Cookie + storage hygiene:
public/listing-set-cookie-flags.spec.ts— session cookies HttpOnly/Secure.public/listing-no-localstorage-leak-on-load.spec.ts— no token/secret keys.public/service-worker-control.spec.ts— sw.js JS content-type.public/non-existent-image-route.spec.ts— fake image paths non-5xx.
Admin API deeper:
api/admin-clients-and-bulk-deeper.spec.ts— clients + bulk + dashboard.api/admin-navigation-and-location-deeper.spec.ts— navigation/location/analytics.api/sponsor-ads-checkout-shapes.spec.ts— sponsor-ads malformed payloads.
Branch: feat/e2e-coverage-1779180779. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 10: tracking params, scanner probes, conditional reqs, viewport (develop-only)
Round 10 of the rolling e2e coverage buildout. 23 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Tracking + framework:
public/listing-with-utm-tracking.spec.ts— UTM/gclid/fbclid/etc.public/nextjs-built-in-routes-tolerance.spec.ts—_next/*probes.public/opengraph-image-routes.spec.ts— App-Router OG image conventions.public/favicons-and-pwa-icons-deeper.spec.ts— mstile/maskable.
Security / hygiene:
public/listing-no-iframe-without-sandbox.spec.ts— 3p iframes sandbox.public/listing-no-dangerous-protocols.spec.ts— no javascript:/data: hrefs.public/common-attack-paths.spec.ts— wp-config/aws/cgi-bin/etc not 200.public/html-no-server-stack-leak.spec.ts— no /var/task/ etc in HTML.public/nextjs-error-boundary-rendering.spec.ts— no raw stack on errors.
Headers / shape:
public/listing-content-type-encoding.spec.ts— Content-Type charset.public/range-requests.spec.ts— Range header tolerance.public/if-modified-since.spec.ts— If-Modified-Since / If-None-Match.public/listing-meta-author-and-keywords.spec.ts— meta author presence.
Routes + URL:
public/dynamic-route-segments-tolerance.spec.ts— unusual URL chars.public/listing-search-redirect-to-detail.spec.ts— /search /s aliases.public/listing-with-anchor-fragment.spec.ts— #fragment tolerance.public/listing-deep-state-restoration.spec.ts— back-button history.
a11y + layout:
public/listing-keyboard-navigation.spec.ts— Tab focuses interactive.public/listing-favorite-button-shape.spec.ts— buttons exist on listings.public/listing-render-on-tablet-viewport.spec.ts— iPad Mini no overflow.public/listing-form-input-counts.spec.ts— auth forms have inputs.
API content types:
api/admin-import-content-types.spec.ts— content-type matrix anonymous.api/admin-export-content-types.spec.ts— Accept header matrix.
Branch: feat/e2e-coverage-1779173518. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 9: perf budgets, console hygiene, navigation flood (develop-only)
Round 9 of the rolling e2e coverage buildout. 26 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Performance budgets:
public/time-to-first-paint-budget.spec.ts— DOMContentLoaded < 15s.public/page-bytes-budget.spec.ts— HTML payload < 1.5MB.public/inline-styles-bandwidth.spec.ts— no 500KB inlinepublic/listing-network-request-count.spec.ts— < 300 requests on /.public/listing-on-mobile-viewport.spec.ts— iPhone 12 no horiz overflow.
Console / runtime hygiene:
public/listing-no-console-errors.spec.ts— no uncaught JS errors.public/listing-no-failed-requests.spec.ts— no unexpected 4xx/5xx in nav.public/listing-image-lazy-loading.spec.ts— off-fold img loading=lazy.public/third-party-script-domains.spec.ts— no http:// scripts.
Sweeps + flood:
public/client-protected-pages-flood.spec.ts— every /client/* anon.public/dashboard-protected-pages-flood.spec.ts— every /dashboard/* anon.public/detail-routes-flood.spec.ts— all detail × sample/missing slugs.public/admin-non-existent-paths.spec.ts— bogus /admin/* paths.public/api-non-existent-paths.spec.ts— bogus /api/* paths not 200.public/sponsorship-prefix-flow.spec.ts— /sponsor with providers.
Listing edges:
public/listing-grid-and-list-toggle.spec.ts— view=grid/list/map/compact.public/listing-sort-options-tolerance.spec.ts— every common sort option.public/listing-filter-clear.spec.ts— empty filter values tolerance.public/listing-search-form-shape.spec.ts— search form is GET method.public/listing-href-locale-prefix.spec.ts— /fr/ links preserve prefix.public/listing-on-detail-page.spec.ts— detail page renders w/o broken img.
Auth flow shape:
public/signout-flow-shape.spec.ts— GET/POST signout non-5xx, with csrf.
Error / shape:
public/error-response-json-shape.spec.ts— 4xx JSON parses as JSON.public/listing-search-via-url-not-leak-secrets.spec.ts— no env names leaked.api/oauth-providers-shape.spec.ts— providers id/name/type contract.api/verify-recaptcha-headers.spec.ts— GET/DELETE/multipart non-5xx.
Branch: feat/e2e-coverage-1779169892. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 8: csrf wire, sitemap shape, RSC sweep, admin verb flood (develop-only)
Round 8 of the rolling e2e coverage buildout. 29 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Auth + CSRF wire:
public/auth-csrf-flow-shape.spec.ts— POST credentials with csrf, non-5xx.public/auth-with-bogus-cookies.spec.ts— every common bogus auth cookie.
SEO shape + listing edges:
public/sitemap-listing-shape.spec.ts—URLs, no /admin/ leak. public/listing-q-injection-shapes.spec.ts— SQLi/JNDI/log4shell probes.public/listing-empty-state-page-shape.spec.ts— empty filter still 200.public/listing-stable-on-repeat.spec.ts— 5x sequential GETs stable.public/listing-form-search.spec.ts— discover search input present.public/listing-jsonld-presence.spec.ts— ld+json count advisory.public/listing-no-stray-localhost.spec.ts— no localhost in prod HTML.public/json-cache-tags-immutability.spec.ts— public JSON Cache-Control.
Detail / RSC:
public/rsc-suffix-on-detail.spec.ts— locale × _rsc on detail routes.public/admin-rsc-suffix.spec.ts— /admin/* anonymous bounce + _rsc.public/client-rsc-suffix.spec.ts— /client/* anonymous bounce + _rsc.public/admin-detail-survey-edit-rsc.spec.ts— admin survey + _rsc.
i18n locale prefix deeper:
public/client-i18n-routes-deeper.spec.ts— locale × client/dashboard.public/admin-i18n-routes-deeper.spec.ts— locale × admin sweep.
Trailing slashes / links:
public/trailing-slash-canonicalization.spec.ts— /about/ no 5xx.public/links-rel-noopener-noreferrer.spec.ts— _blank links carry rel.
API rejection:
public/favorites-api-routes-deeper.spec.ts— favorites verbs anonymous.public/admin-method-flood.spec.ts— every verb × top admin endpoints.api/featured-items-deeper.spec.ts— featured items query shapes.api/reference-deeper.spec.ts— /api/reference query shapes.api/sponsor-ads-list-deeper.spec.ts— sponsor-ads list + mutating.api/admin-collections-comments-deeper.spec.ts— collections + items.api/admin-notifications-deeper.spec.ts— mark-all-read variants.api/admin-sponsor-ads-deeper.spec.ts— approve/reject/cancel anonymous.api/admin-reports-deeper.spec.ts— reports detail anonymous.api/stripe-portal-and-products-edges.spec.ts— stripe portal variants.
Image / SEO:
public/image-with-srcset-shape.spec.ts— srcset entries well-formed.
Branch: feat/e2e-coverage-1779166258. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 7: deeper API + SEO link shape + locale prefixes (develop-only)
Round 7 of the rolling e2e coverage buildout. 30 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
SEO + link shape:
public/rel-next-prev-listing.spec.ts— rel=next/prev hrefs sane.public/canonical-link-presence.spec.ts— canonical href non-empty.public/robots-disallow-shape.spec.ts— admin/internal protected.public/sw-html-payload-shape.spec.ts— doctype + html/head/body close.public/preconnect-and-dns-prefetch.spec.ts— preconnect hrefs well-formed.public/listing-pagination-link-validity.spec.ts— pagination links internal.
Detail routes deeper:
public/item-detail-deeper-tolerance.spec.ts— /items/[slug] weird inputs.public/collections-detail-deeper.spec.ts— /collections/[slug] weird.public/surveys-detail-deeper.spec.ts— /surveys/[slug] weird.public/detail-page-rsc-tolerance.spec.ts— RSC prefetch on detail pages.
i18n / locale prefix:
public/multiple-locale-meta.spec.ts— each locale title + /about + /discover.public/admin-locale-prefix.spec.ts— //admin bounces anonymous. public/client-area-locale-prefix.spec.ts— //client + / /dashboard.
Header tolerance:
public/fetch-with-priority-hints.spec.ts— Save-Data, Priority, DPR.public/dnt-tolerance.spec.ts— DNT/Sec-GPC/Sec-Fetch-* tolerance.public/theme-class-tolerance.spec.ts— html class no "undefined".public/dom-no-react-errors.spec.ts— no error overlay text in HTML.
Listing edges:
public/listing-deep-link-share.spec.ts— bookmarked filter URLs.public/ai-chat-toggle-tolerance.spec.ts— ?chat=open/closed survives.public/listing-prefetch-burst-paths.spec.ts— parallel burst across routes.public/error-route-handlers.spec.ts— /error /not-found /loading direct.public/submit-and-extract-anon.spec.ts— /submit anonymous behavior.
JSON / discovery / health:
public/swagger-and-openapi-shape.spec.ts— no secrets in openapi docs.public/health-shape.spec.ts— /api/health no DB URL leak.public/agent-and-crawl-discovery.spec.ts— agents.json/ai.txt non-5xx.public/misc-feed-aliases.spec.ts— /rss /atom /feed alias non-5xx.public/public-listing-json-mirror-deeper.spec.ts— .json mirrors parse.
Admin API sweep + deeper:
public/admin-prefix-api-rejection.spec.ts— every admin GET 4xx.public/client-prefix-api-rejection.spec.ts— every client GET 4xx.api/admin-settings-deeper.spec.ts— settings PUT/PATCH/DELETE/OPTIONS.api/admin-categories-deeper.spec.ts— categories + reorder + git.api/admin-users-deeper.spec.ts— users + check-email/username.api/admin-items-deeper.spec.ts— items + bulk + import + export.api/admin-tags-roles-deeper.spec.ts— tags + roles + permissions.api/version-and-tenant-edge.spec.ts— version/sync/tenant edges.api/http-overrides-tolerance.spec.ts— method override no auth bypass.api/polar-and-solidgate-deeper.spec.ts— empty/hostile body POSTs.
Branch: feat/e2e-coverage-1779162628. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 6: PWA / RSC / CORS / a11y deeper (develop-only)
Round 6 of the rolling e2e coverage buildout. 35 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
OG / SEO / feeds:
public/og-image-and-twitter-card.spec.ts— og:title/desc + twitter:card.public/rss-atom-feed-shape.spec.ts— feed XML/JSON shape contracts.public/listing-jsonld-itemlist.spec.ts— listing pages json-ld parses.
Static assets / caching / image optimizer:
public/nextjs-image-route-tolerance.spec.ts—/_next/imagehostile inputs.public/static-chunk-caching.spec.ts—/_next/static/*immutable cache.public/favicon-detail.spec.ts— all common favicon paths non-5xx.public/static-data-routes.spec.ts—/_next/dataand chunk probes.public/service-worker-and-manifest-tolerance.spec.ts— PWA endpoints.public/image-domain-allowlist.spec.ts— homepage<img src>resolve.
HTTP method matrix / headers:
public/webhook-method-coverage.spec.ts— webhooks reject non-POST.public/options-method-tolerance.spec.ts— preflight OPTIONS non-5xx.public/head-method-tolerance.spec.ts— HEAD requests non-5xx.public/cors-on-public-api.spec.ts— no wildcard CORS with credentials.public/hsts-and-redirect-headers.spec.ts— HSTS max-age plausible.public/custom-host-and-x-forwarded.spec.ts— proxy header tolerance.
Auth / form shape:
public/login-modal-and-form-shape.spec.ts— email/password inputs render.public/form-input-types.spec.ts— type=email / type=password correctness.public/autocomplete-attributes.spec.ts— password autocomplete not "off".public/auth-error-page-shape.spec.ts— every NextAuth error code non-5xx.public/auth-callback-routes.spec.ts— provider callback non-5xx.
a11y deeper:
public/heading-and-landmarks-quick.spec.ts— main/nav/h1 presence.public/skip-link-and-focus.spec.ts— skip link or main[id], Tab works.public/images-have-alt.spec.ts— everyhas alt attribute.
public/html-charset-and-viewport.spec.ts— meta charset + viewport.
Inline / CSP / chat / extract:
public/inline-script-csp-shape.spec.ts— unsafe-inline requires nonce.public/chat-api-protection.spec.ts— /api/chat anonymous, no key leak.public/extract-api-protection.spec.ts— /api/extract SSRF tolerance.
Listing / RSC / preview:
public/listing-rsc-stream-tolerance.spec.ts— RSC header + _rsc=.public/nextjs-prefetch-headers.spec.ts— purpose=prefetch tolerance.public/listing-no-duplicates.spec.ts— items.json no duplicate slugs.public/listing-aria-current-and-state.spec.ts— weird filter survival.public/listing-edge-pagination-overshoot.spec.ts— page 9999 non-5xx.public/listing-items-engagement-shape.spec.ts— engagement query shapes.public/export-routes-tolerance.spec.ts— items export with bad params.public/geocode-and-location-deeper.spec.ts— geocode hostile inputs.public/public-route-rsc-prefetch-burst.spec.ts— parallel prefetch burst.public/preview-mode-and-draft.spec.ts— /api/preview /api/draft non-200.public/rate-limit-anonymous-deeper.spec.ts— rapid auth-adjacent posts.public/stripe-redirect-and-checkout-routes.spec.ts— pricing/success vars.
Settings / admin / page protection:
public/settings-deep-paths-protected.spec.ts— client/settings/* gate.public/admin-survey-routes-anonymous.spec.ts— admin/surveys/* gate.public/admin-detail-pages-anonymous.spec.ts— /admin/clients/[id] gate.
API deeper:
api/items-comments-rating-deeper.spec.ts— nested comments/rating.api/admin-companies-and-comments-deeper.spec.ts— admin orgs/comments.
Branch: feat/e2e-coverage-1779159025. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 5: comparisons / CMS / dashboard surveys / extra hardening (develop-only)
Round 5 of the rolling e2e coverage buildout. 56 new spec files added on
develop only (no cascade per operator instructions). Focus areas:
Uncovered pages and deeper boundaries:
public/comparisons-detail-public.spec.ts— /comparisons/[slug].public/cms-pages-slug.spec.ts— /pages/[slug] CMS bucket.public/categories-detail-tolerance.spec.ts— /categories/[…] + nested catch-all.public/tags-detail-tolerance.spec.ts— /tags/[…] + nested catch-all.public/discover-deep-pages.spec.ts— sort/view/q combinations and edge pages.public/listing-deeper-paging-boundaries.spec.ts— huge / negative / scientific page numbers.public/listing-modifier-deeper.spec.ts— duplicate sort/view params, utm_source noise, repeated keys.public/listing-search-q-shapes.spec.ts— control chars, unicode, oversize q.public/paging-tags-and-collections.spec.ts— /tags/paging/[N] + /collections/paging/[N] edges.public/items-detail-survey-slug.spec.ts— /items/[slug]/surveys/[slug].public/map-route-tolerance.spec.ts— /map with bogus zoom/bbox/lat/lng.public/submit-page-tolerance.spec.ts— /submit query variations.public/sponsor-public-routes.spec.ts— /sponsor with bogus plan/coupon.public/pricing-success-deeper.spec.ts— /pricing/success with every provider.public/auth-pages-deeper-tolerance.spec.ts— auth pages with bogus tokens, callbackUrls, error codes.public/favorites-anonymous-tolerance.spec.ts— /favorites anonymous gate.
Robust input + header tolerance:
public/content-length-and-encoding.spec.ts— JSON content-type + bogus accept-encoding.public/cookie-tolerance.spec.ts— empty / malformed / oversize / bogus session cookies.public/user-agent-and-referer.spec.ts— empty/bot/long UA, weird Referer.public/api-large-query-string.spec.ts— 10kB qs, 200 params, unicode, NULs.public/concurrent-rapid-navigation.spec.ts— rapid navigation + reload x5.
Security / SEO / a11y headers:
public/error-page-status-codes.spec.ts— bot-path 404s + wp-login, .env etc.public/sitemap-and-robots-shape.spec.ts— sitemap.xml/robots.txt/llms.txt shape.public/static-asset-coverage.spec.ts— favicon/manifest/etc must non-5xx.public/structured-data-jsonld.spec.ts— script[type=ld+json] valid JSON.public/head-meta-essentials.spec.ts— title + meta description per route.public/html-lang-and-dir.spec.ts— html[lang] presence + ar dir=rtl.public/html-no-mixed-content.spec.ts— no http:// asset src on HTTPS.public/open-redirect-defense.spec.ts— callbackUrl can't escape origin.public/response-headers-deeper.spec.ts— X-Powered-By, Server, frame protection.public/internal-and-internal-routes-protected.spec.ts— /api/internal/db-init + /api/test/* never 200.public/session-and-csrf-shape.spec.ts— wire contracts for /api/auth/session / csrf / providers.public/language-locale-redirects.spec.ts— locale prefix tolerance.public/cms-md-mirror-deeper.spec.ts— pages/.md + items/.md mirrors.public/webfinger-and-well-known.spec.ts— common .well-known probes.public/http-methods-on-public-api-deeper.spec.ts— DELETE/PUT/PATCH on public GETs returns 4xx not 5xx.public/link-from-header-and-nav.spec.ts— navigation chain + back-button.public/links-anchors-and-buttons.spec.ts— no href="undefined", labeled buttons.
Client / dashboard:
client/dashboard-items-surveys-protected.spec.ts— /dashboard/items/[id]/surveys.client/client-users-and-sponsorships-protected.spec.ts— anonymous gate.client/public-profile-deeper.spec.ts— /client/profile/[u]/followers/following with weird usernames.
API rejection deeper:
api/admin-detail-deeper.spec.ts— every admin detail endpoint anonymous.api/admin-bulk-and-import-rejection.spec.ts— bulk/import/export endpoints.api/user-profile-deeper.spec.ts— /api/user/profile/follow + portfolio.api/stripe-products-deeper.spec.ts— stripe products / portal / subs.api/polar-deeper.spec.ts— polar checkout / portal / webhook.api/sponsor-ads-user-deeper.spec.ts— sponsor-ads user endpoints.api/items-engagement-deeper.spec.ts— items engagement nested routes.api/client-items-and-imports-rejection.spec.ts— client items + imports.api/reports-and-surveys-deeper.spec.ts— reports + surveys deeper.api/payment-and-current-user-deeper.spec.ts— payment/account, current-user.api/auth-nextauth-discovery-deeper.spec.ts— providers/csrf/session shape.api/internal-cron-protection.spec.ts— cron routes require secret.api/verify-recaptcha-deeper.spec.ts— synthetic tokens rejected.api/reference-and-tenant-shape.spec.ts— no secrets leaked in discovery.api/body-content-types-rejection.spec.ts— malformed body / wrong CT across POST endpoints.
Branch: feat/e2e-coverage-1779156184. Admin-merged once CI passes. No
cascade to stage / main per operator instructions.
2026-05-19 — Spec 028 round 4: payment providers + admin sub-systems (develop-only)
Round 4 of the rolling e2e coverage buildout. 22 new spec files added
on develop only (no cascade per operator instructions). Focus areas:
Payment providers + Stripe:
api/polar-endpoints-rejection.spec.ts— Polar user endpoints + webhook signature gate.api/lemonsqueezy-endpoints-rejection.spec.ts— LemonSqueezy + webhook signature gate.api/solidgate-endpoints-rejection.spec.ts— Solidgate + webhook signature gate.api/stripe-payment-methods-rejection.spec.ts— Stripe payment method + subscription per-id endpoints reject anonymous.api/payment-account-rejection.spec.ts— /api/payment/* + sponsor-ads checkout reject anonymous.
Items engagement:
api/items-engagement-public.spec.ts— votes/comments/views/ activity public reads + anonymous mutation rejection.api/favorites-itemslug-rejection.spec.ts— favorites slug-keyed mutations require auth.
Client API:
api/client-items-api-rejection.spec.ts— /api/client/* endpoints reject anonymous (GET + mutating).
Admin sub-systems:
api/admin-roles-and-permissions.spec.ts— roles + permissions graph endpoints.api/admin-users-and-validation.spec.ts— users + check-email / check-username (anti-enumeration).api/admin-sponsor-ads-and-twentycrm.spec.ts— admin sponsor-ads CRUD + TwentyCRM config endpoints + settings/map-status.api/admin-tags-and-export.spec.ts— admin tags + items export/import.api/admin-clients-and-search.spec.ts— admin clients + advanced-search + notifications.
Public utility endpoints:
api/location-and-geocode-public.spec.ts— location/cities/ countries/coordinates/search + geocode tolerance.api/public-misc-endpoints.spec.ts— exists probes, featured-items, reference, health/database (no credential leak), items export settings, config/features, /api/internal/db-init rejection.
Public hardening:
public/service-worker-and-pwa.spec.ts— manifest + sw reachability.public/favicon-and-apple-touch.spec.ts— browser icon assets reachable + advertised in .public/third-party-iframe-tolerance.spec.ts— page renders when Stripe / reCAPTCHA / analytics are blocked.public/http-method-on-pages.spec.ts— HEAD/OPTIONS/PUT/DELETE on pages don't 5xx.public/directory-traversal-defense.spec.ts— path-traversal / null-byte / long-slug / homograph probes.public/meta-robots-controls.spec.ts— public pages indexable; robots.txt doesn't disallow everything.public/hash-fragment-tolerance.spec.ts— unknown / hostile URL hashes don't crash pages; signin doesn't auto-sign-in from a hash.public/listing-rss-shape.spec.ts— empty-data feed tolerance (channel meta present even when no items).public/meta-tags-twitter-and-opengraph.spec.ts— og:title / og:type / og:image / twitter:image present where expected.public/rate-limit-search.spec.ts— 20 rapid /api/items.json requests and parallel /discover searches don't 5xx.
Auth:
auth/oauth-error-page-tolerance.spec.ts—/auth/error?error=…tolerates 16 different codes (including XSS / traversal payloads); /auth/signout + /auth/verify-request render.
2026-05-19 — Spec 028 round 3: hourly continuation batch (develop-only)
Round 3 of the rolling e2e coverage buildout. 28 new spec files added
on develop only (no cascade per operator instructions). Focus areas:
API security matrices:
api/stripe-endpoints-rejection.spec.ts— Stripe surface + webhook signature rejection.api/surveys-api-rejection.spec.ts— surveys read tolerance + mutation rejection.api/sponsor-ads-api-rejection.spec.ts— sponsor-ads user API anonymous rejection.api/items-public-api.spec.ts— public items.json contract + garbage-query tolerance.api/cron-and-webhook-security.spec.ts— cron endpoints require CRON_SECRET; platform activity-feed rejects bad HMAC.api/recaptcha-tenant-version.spec.ts— boundary endpoint shapes.api/comments-votes-favorites-rejection.spec.ts— engagement endpoints reject anonymous mutations.api/admin-detail-api-rejection.spec.ts— admin per-resource GETs reject anonymous.api/user-profile-mutating.spec.ts— profile mutating endpoints reject anonymous.
Admin pages + deeper API:
admin/admin-detail-and-survey-routes.spec.ts— admin dynamic-segment routes for client and survey detail/create.admin/admin-mutating-api-deeper.spec.ts— sweep of remaining admin POST/PATCH/DELETE endpoints.admin/admin-test-mock-endpoint-disabled.spec.ts—/api/__test__/*routes are gated.admin/admin-i18n-locale.spec.ts— admin pages in fr/es/de.
Auth coverage:
auth/new-password-flow-tokens.spec.ts— token-gated auth pages tolerate garbage / SQL-shaped / missing tokens.auth/admin-signin-page.spec.ts—/admin/auth/signinform + callbackUrl sanitization.
Client coverage:
client/settings-form-elements.spec.ts— every settings sub-page exposes form elements.client/public-profile-view.spec.ts— non-existent profile slugs 404, special chars tolerated.client/client-i18n-locale.spec.ts—/client/*in fr/es/de.
Public coverage:
public/item-detail-and-survey.spec.ts— item detail + .md mirror + collections/comparisons/pages slug tolerance.public/paging-deep-routes.spec.ts— tags/collections paging edge inputs + nested category/tag catch-alls.public/response-content-types.spec.ts— HTML pages serve text/html; JSON endpoints serve application/json.public/listing-sort-and-view-toggle.spec.ts— sort + view-toggle URL state combinations.public/static-info-pages-content.spec.ts— about/help/legal pages have substantive content.public/xss-payloads-tolerated.spec.ts— classic XSS payloads in query/slugs/hash don't execute.public/webfinger-and-discovery.spec.ts— well-known + llms.txt manifests.public/swagger-and-openapi.spec.ts— Swagger/OpenAPI endpoints (if present) respond.public/focus-and-keyboard-nav.spec.ts— keyboard tab order + Enter on submit button.public/viewport-and-mobile.spec.ts— mobile + tablet rendering without horizontal overflow.public/listing-json-mirror.spec.ts— listing JSON peer responds under filter combos.public/link-from-detail-back-to-list.spec.ts— forward/back nav preserves URL state.public/hot-route-perf-budget.spec.ts— request count + HTML doc size on hot routes.public/fetch-cache-busting.spec.ts— tracking params don't break caching.public/admin-api-prefix-rejection.spec.ts— unknown /api/admin/* paths reject cleanly.public/stripe-redirect-routes.spec.ts— /pricing CTAs don't crash anonymously.public/dashboard-billing-route.spec.ts— /dashboard/billing access matrix.public/ai-chat-disabled-tolerance.spec.ts— chat plugin disabled state.
2026-05-19 — Spec 028 round 2: large coverage batch (develop-only, no cascade)
Operator asked for a 30-min batch focused on coverage breadth; do NOT cascade to stage/main until coverage is comprehensive. Updated the hourly CCDB task #68 prompt to skip the cascade step until further notice.
Files added (apps/web-e2e/tests/...):
admin/admin-route-coverage-matrix.spec.ts— all 16 admin pages, 3 personas (admin OK, client denied, anonymous gated).admin/admin-api-mutating-rejection.spec.ts— POST/PATCH/DELETE on admin endpoints reject anonymous with 4xx (not 5xx, not 200).api/admin-api-coverage-matrix.spec.ts— 21 admin GET endpoints, anonymous rejection + admin acceptance.api/user-api-coverage-matrix.spec.ts—/api/user/*per-user GET endpoints, anonymous rejection + authenticated acceptance.api/http-methods-coverage.spec.ts— POST/DELETE on GET-only endpoints returns 4xx (not 5xx); OPTIONS doesn't crash.api/rate-limit-shape.spec.ts— malformed-auth-input shape + forgot-password no-enumeration check.client/client-area-route-matrix.spec.ts— every/client/*route loads for the authenticated client.client/dashboard-route-matrix.spec.ts— legacy/dashboard/*surface (billing + item surveys) — admin + client + anonymous.client/client-api-mutating-rejection.spec.ts— mutating/api/user/*rejects anonymous.client/submission-create-flow.spec.ts—/submitform renders for clients, blocks empty submit, gated for anonymous.auth/auth-flow-comprehensive.spec.ts— register → dashboard → signout → signin → dashboard round-trip; bad-password error UI; duplicate-email error UI.auth/form-validation-comprehensive.spec.ts— HTML5 required-field- email-format validation across signin/register/forgot-password.
auth/callback-url-sanitization.spec.ts— hostile?callbackUrl=values don't open-redirect.i18n/locale-coverage-matrix.spec.ts— 6 locales × 6 core pages, assert non-5xx + heading +<html lang>matches URL.public/route-coverage-matrix.spec.ts(round 1, already shipped), plus this round:public/seo-meta-coverage.spec.ts— title/description/canonical/ og:title/JSON-LD on every key page.public/sitemap-feeds-shape.spec.ts— sitemap.xml is XML+urlset, rss/atom/json feeds parse, opengraph-image is an image.public/hreflang-coverage.spec.ts— alternates emitted on every locale-aware page.public/meta-rss-discovery.spec.ts— feed autodiscovery links.public/md-mirror-routes.spec.ts—.mdmirror of static info pages serves Markdown.public/header-footer-completeness.spec.ts— header + footer render on every sampled page.public/security-headers.spec.ts— nosniff, XFO, HSTS, CSP on every page + API.public/caching-headers.spec.ts— home is CDN-cacheable; auth endpoints aren't.public/redirect-canonicalization.spec.ts— trailing slash,/en/prefix, case variants don't 5xx.public/concurrent-anonymous-sessions.spec.ts— two anonymous contexts have independent sessions / CSRF tokens.public/links-no-broken-internal.spec.ts— sampled internal links from every seed page resolve.public/images-and-icons.spec.ts— favicon, logo, accessibility hints on logo.public/noscript-fallback.spec.ts— public pages render with JavaScript disabled.public/listing-filter-combinations.spec.ts— listing filter combinations (search × sort × page).public/listing-pagination-edges.spec.ts— non-numeric / negative / huge page params don't 5xx.public/listing-empty-state.spec.ts— no-result search, empty category, anonymous favorites.public/404-and-error-recovery.spec.ts— 404 renders nav, home link works, non-existent slugs return 4xx not 5xx.public/page-stability-fresh-cookies.spec.ts— every public page survives a completely empty cookie jar.public/trailing-rsc-suffix.spec.ts—?_rsc=…RSC prefetch queries don't 5xx (the prefetch pattern Spec 027 caught in network logs).public/large-payload-handling.spec.ts— 5000-char email / 1000-char name / 2000-char search query rejected with 4xx.public/sponsor-pricing-success-redirect-loop.spec.ts—/pricing/successwith no/garbage params doesn't loop.public/theme-and-prefs-persistence.spec.ts— theme toggle + locale cookie persistence across navigation.public/accessibility-quick-audit.spec.ts— axe-core WCAG 2A/AA on home + signin + register; fail on critical/serious only.public/performance-budget-public.spec.ts— total JS bytes transferred on first load is under a generous ceiling; home loads within 10s.public/json-api-shapes.spec.ts— minimum shape contract for public JSON endpoints (version, items.json, csrf, providers, session, current-user, currency).
All changes land on develop only via PR; per operator instruction,
no cascade to stage/main this round. The hourly task #68 picks up
the next gap iteration.
2026-05-19 — Spec 028 round 1: CI workflow + initial gap-filling specs
.github/workflows/e2e.yml— Postgres-backed, sharded 4-way Playwright workflow. Runs on push to main/develop/stage and on PR. The repo had 311.spec.tsfiles but no CI workflow; Spec 027 would have been caught before merge if any of them ran. They now do.apps/web-e2e/tests/client/followers-following.spec.ts— adds/client/profile/[username]/followersand/followingcoverage (previously a gap; spec-022 shipped the routes without specs).apps/web-e2e/tests/client/settings-subroutes.spec.ts— matrix of every/client/settings/profile/*and/client/settings/securitysub-page: authenticated client gets non-5xx + heading + no signin-bounce; anonymous gets the signin redirect. Catches the entire class of "shipped a new settings tab and forgot a server fetch" regressions.apps/web-e2e/tests/public/route-coverage-matrix.spec.ts— anonymous walk of every public page in the App Router with a "never 5xx" assertion. Includes feed manifests + a 404 sanity check.apps/web-e2e/tests/api/public-api-coverage-matrix.spec.ts— twin matrix for API routes: public ones respond JSON, protected ones reject anonymous with 401/403 (not 5xx, not 200).- New
docs/spec/028-e2e-coverage-buildout/spec.mddocuments the workstream + the hourly self-scheduling agent that fills more gaps iteration by iteration.
2026-05-19 — Spec 027 round 7: revert to client-side signIn on register (final fix)
Rounds 4–6 chased a wrong hypothesis (server-side auth() failing on
Server Components, getSessionViaApi workarounds). The actual smoking
gun, found via a diagnostic /api/debug-session-cookies route: after
the round-1 server-side signIn in signUp, the response set
__Secure-authjs.session-token correctly, but the very next request
(the form's refreshSession() calling /api/auth/session) received
back a Set-Cookie that cleared the session-token. Auth.js v5
beta.30's server-action signIn writes the cookie along a different
encrypt-/sign-path than the standard /api/auth/callback/credentials
flow uses to verify; the mismatch makes the verifier think the cookie
is invalid and clear it.
The sign-in form's path (client-side signIn('credentials', …) from
next-auth/react) does NOT have this regression — it goes through
the same /api/auth/callback/credentials endpoint that /api/auth/session
verifies against, so the two stay in agreement.
This round:
apps/web/app/[locale]/auth/actions.ts—signUpreturns{ autoLogin: true, … }again, identical shape tosignInAction. No more server-sideserverSignIncall from inside the action. Removes the unusedsignIn as serverSignInimport and theisNextRedirectErrorhelper.apps/web/lib/auth/get-session-via-api.ts— deleted (no longer used).apps/web/app/api/debug-session-cookies/route.ts— deleted (temporary diagnostic, served its purpose).apps/web/app/[locale]/client/{dashboard,settings,submissions,submissions/trash,sponsorships,users,profile/[username]}/page.tsx— reverted toawait auth(). Round 2'sforce-dynamicstays (still the right thing). Round 3'sruntime='nodejs'reverted — it didn't help and wasn't needed.
The round-1 autoLoginFiredRef / successHandledRef useRef guards
in credentials-form.tsx STAY — they were the actual bug-A fix and
prevent the double-fire that originally motivated all of this.
2026-05-19 — Spec 027 round 4: getSessionViaApi() to bypass Auth.js v5 page-context bug
- Round 3's
runtime = 'nodejs'still failed: same fetch context,/api/auth/sessionreturned{user: …}but/client/dashboardreturned 307 to/auth/signinwith the same cookie attached. Theauth()export from Auth.js v5 beta.30 is silently returning null when called from a Server Component page under this app's specific combination of Next 16.2.6 + next-intl plugin + Sentry plugin (suspect either the next-intlProxy (Middleware)strips/reshapes the headers, or Auth.js'sheaders().get("cookie")path doesn't see them; either way the upstream library is the wrong layer to fix in this PR). - Workaround: new helper
apps/web/lib/auth/get-session-via-api.tsthat reads cookies viacookies()from next/headers and forwards them to/api/auth/session(which already works correctly). Returns null on no session; never throws. Cost: one same-region HTTP roundtrip per auth-gated page render — bounded because these pages are alreadyforce-dynamic. - Swapped
await auth()forawait getSessionViaApi()on all auth-gated client pages:dashboard,settings,submissions,submissions/trash,sponsorships,users,profile/[username]. - Spec 027 diagnosis updated; this PR ships the user-visible fix.
2026-05-18 — Spec 027 round 3: runtime='nodejs' on auth-gated client pages
- Round 2's
force-dynamicpatch made the pages dynamic but they STILL returned 307 to/auth/signinfor valid sessions. Directcontext.request.getcalls confirmed the cookie was reaching the server andGET /api/auth/sessionfrom the same fetch context returned{user: {...}}happily — butGET /client/dashboardreturned 307 anyway. The asymmetry was runtime:app/api/auth/[...nextauth]/route.tspinsruntime = 'nodejs'so Auth.js v5's JWT callbacks (which pulltenantIdfrom Drizzle and usebcryptjsin the credentials provider — all three inserverExternalPackagesand unbundlable for the Edge runtime) can actually execute; the Server Component pages defaulted to whatever Vercel chose, soauth()there silently returnednull. apps/web/app/[locale]/client/{dashboard,settings,submissions,submissions/trash,sponsorships,users,profile/[username]}/page.tsx— addedexport const runtime = 'nodejs'.- Spec 027 diagnosis updated; this is the round that lands the actual user-visible fix.
2026-05-18 — Spec 027 follow-up: force-dynamic on auth-gated client pages
- After PR #853 landed the server-side signIn fix, re-running the
Playwright repro against demo.ever.works showed the session cookie
was now being set on
POST /auth/register— but/client/dashboardstill redirected to/auth/signineven with a valid cookie attached, while/api/auth/sessionand/api/current-userhappily returned the user. The asymmetry was Next.js statically pre-rendering the no-sessionredirect('/auth/signin')and serving it cached. apps/web/app/[locale]/client/{dashboard,settings,submissions,submissions/trash}/page.tsx— addedexport const dynamic = 'force-dynamic'.client/sponsorshipsandclient/usersalready had it; these four were the missing ones.- Spec 027 diagnosis updated to document both halves of the fix and why each is necessary on its own.
2026-05-18 — Spec 027: fix post-register auto-login race (template prod)
spec-027filed underdocs/spec/027-fix-post-register-autologin/and indexed indocs/spec/README.mdas shipped (alongside this PR).apps/web/app/[locale]/auth/actions.ts—signUpnow issues the session cookie itself via server-sidesignIn()from Auth.js v5, so theSet-Cookieheader rides in the same response as the success body; returns{ autoLoggedIn: true }instead of asking the client to sign in.apps/web/app/[locale]/auth/components/credentials-form.tsx— addedsuccessHandledRef/autoLoginFiredRefuseRefguards so neither the new server-side path nor the legacy client-side auto-login path can re-fire afteruseEffectre-runs from identity-unstable deps (refreshSession,invalidateAllUserData,tCred). This was the prod smoking gun: a secondsignInfetch aborted by navigation, surfacing asTypeError: Failed to fetchin console and racing the cookie write against the dashboard'sauth()call.
2026-05-19 — Spec 029: renumber from 027 → 029 after develop landed 027/028
spec-029Renumbered this spec from027→029when rebasing ondevelop, becausedevelophad concurrently landedspec-027-fix-post-register-autologinandspec-028-e2e-coverage-buildout. Folder, frontmatter (id,title,sidebar_label), in-body header, and the README index row were rewritten from027→029. Earlierspec-027references in this log that belong to the Preferences-section work were rewritten tospec-029/029-…; references that belong to the post-register auto-login fix (also numbered 027 ondevelop) were left alone. No code changes in this commit. PR #850.
2026-05-18 — Spec 029: e2e coverage + PR-number backfill for Preferences section
spec-029Added Playwright coverage underapps/web-e2e/tests/client/settings.spec.tsasserting the new Preferences section is reachable from/client/settingsand that the three always-on block headings (Layout / Container Width / Pagination Style) render — addresses Augment-review feedback that user-visible changes need at least one e2e assertion perAGENTS.md§9. PR #850.
2026-05-18 — Spec 029: align preference block components with /client/settings
spec-029Visual refresh on the six block components rendered in bothSettingsModaland the new/client/settingsPreferences section:SelectLayout,SelectContainerWidth,SelectPaginationType,SelectDatabaseMode,SelectCheckoutProvider,DatabaseStatusWarning. Each card drops the glassmorphic surface (bg-white/80 dark:bg-white/[0.04], faintborder-...[0.07],group,transition-all,p-5) for the page-card flat treatment (bg-white dark:bg-[#111111],border-gray-200 dark:border-white/6,shadow-sm,p-4). Icon containers swapbg-gray-100 dark:bg-white/5 p-2withh-5 w-5 text-gray-400icons for the flat tintedw-8 h-8 bg-theme-primary-50 dark:bg-theme-primary-900/30 rounded-lgsquare withw-4 h-4 text-theme-primary-600icons — matchingSettingsCard. Title typography goes fromtext-base font-semibold leading-tighttotext-sm font-semibold tracking-tight; description fromtext-sm text-gray-600 leading-relaxed mt-1totext-xs text-gray-500 mt-0.5.DatabaseStatusWarningadditionally has its mismatched gray-icon + blue-title normalized to the same neutral typography as the other blocks. Layout-option buttons,SegmentedToggle,Selectdropdown, amber sub-warning, toast feedback, and alluseLayoutThemewiring untouched. Spec §6 updated. PR #850.
2026-05-18 — Spec 029: align SettingsModal with /client/settings visual language
spec-029SettingsModalsurface drops glassmorphism (bg-white/95 backdrop-blur-xl,rounded-2xl, border/[0.07]) for the page-card flat treatment (bg-white dark:bg-[#111111],border-gray-200 dark:border-white/6,rounded-xl). Backdrop simplifies from a heavy gradient +backdrop-blur-2xl backdrop-saturate-150tobg-black/40 dark:bg-black/60 backdrop-blur-sm. Header drops the gradient bg + shadow, replaces the gradient/bordered icon container with the page's flat tinted square (w-8 h-8 bg-theme-primary-50 dark:bg-theme-primary-900/30 rounded-lg+w-4 h-4 text-theme-primary-600), and dials the title fromtext-xl font-boldtotext-base font-semibold tracking-tightmatching the user-card name treatment. Close button loseshover:scale-110and uses the page's hover bg. Focus trap, Esc-to-close, body-scroll lock, andanimate-fade-in-upentry all preserved. Spec updated under §6 Implementation Notes. PR #850.
2026-05-18 — Spec 029: align header SettingsButton with /client/settings icon style
spec-029Header gear button (apps/web/components/settings-button.tsx) now uses the same tinted theme-primary square theSettingsCardicons use on/client/settings(w-8 h-8 bg-theme-primary-50 dark:bg-theme-primary-900/30 rounded-lgwrapper,w-4 h-4 text-theme-primary-600 dark:text-theme-primary-400icon, hover bumps the wrapper tint). Replaces the previous gray-on-transparent icon +hover:scale-105treatment so the header entry point reads as the same control system as the page.FloatingSettingsButtonintentionally untouched — its solid theme-primary fill is the deliberate shortcut affordance. Spec updated under §6 Implementation Notes. PR #850.
2026-05-17 — Spec 029: client-settings Preferences section
spec-029Drafted spec atdocs/spec/029-client-settings-preferences-section/spec.mdand indexed indocs/spec/README.md. Embeds theSettingsModalblock components (SelectLayout,SelectContainerWidth,SelectPaginationType, plus demo-onlySelectDatabaseMode,SelectCheckoutProvider,DatabaseStatusWarning) inline as a new Preferences section on/client/settingsso the visual-preference controls are reachable from the settings hub. The modal stays exactly as-is for shortcut access from the header gear and floating button. Page-local primitives only — no shared settings shell extracted in this PR. Adds one new i18n key (settings.PREFERENCES) to all 21 locale files. PR #850.
2026-05-18 — Spec 027 client notifications system (PR #852)
spec-027New specdocs/spec/027-client-notifications/(spec + plan + tasks) delivering the client-facing surface of013-notifications-system: header bell + dropdown,/client/notificationsinbox,/client/notifications/preferencesmatrix, SSE real-time delivery, and a service-layerdispatch()that resolves per-user preferences, applies group-key deduplication, and fans out through in-memory pub/sub (Redis-ready).spec-027Additive Drizzle migration0037_client_notifications.sql: 22 new enum values,priority/category/actorId/groupKey/archivedAt/deliveredChannelscolumns, partial unread index, and a newnotification_preferencestable with JSONB channel matrix + email digest cadence + quiet hours.indexdocs/spec/README.mdindexed entry 027.
2026-05-17 — Spec 026 (EW-627) round 5: chart visual redesign
spec-026Extended Spec 026 with §7 covering the visual redesign of the five focus chart cards on/client/dashboard: Submission Timeline, Submission Status, Weekly Activity, Community Engagement, and Approval Rate Trend. Acceptance criteria AC-18 through AC-23 added.spec-026New shared primitive moduleapps/web/components/dashboard/_chart-primitives.tsxexporting<ChartCard>,<ChartCardSkeleton>,<ChartEmptyState>,<ChartLegend>/<ChartLegendItem>,<ChartKpi>,<ChartTooltip>,useChartAxisProps(), andformatCompactNumber(). Replaces ad-hoc per-chart chrome + Recharts defaults across all five redesigned cards.spec-026Replaced the cramped 3-slice pie in Submission Status with a horizontal stacked bar over a per-status row list (icon chip + count- percent). Reads at a glance and scales better with future statuses.
spec-026Weekly Activity now flows all three series labels ("Submissions", "Views", "Engagement") throughuseTranslations()— previously hard-coded English bypassed the i18n layer entirely.spec-026Community Engagement converted from a flat pie with overlapping labels to a donut with the total in the centre + side legend with per-slice value and percent. Stacks belowsm.spec-026Backfilled 29 new chart-redesign i18n keys across all 20 non-English locale files with real translations (SUBMISSION_TIMELINE.*,ACTIVITY_CHART.*, plus extensions toSTATUS_BREAKDOWN.*andENGAGEMENT_CHART.*). Backfill script used once and removed.
2026-05-15 — Spec 026 (EW-627) round 2: layout v2 + avatar fix + i18n backfill
spec-026Extended Spec 026 with §6 covering the new dashboard layout (header / quick actions / alerts / mobile summary / four content tabs), the per-card trend deltas + zero-state CTAs inStatsCard, and the avatar fix. Acceptance criteria AC-10 through AC-17 added.spec-026Removed the prototype7d / 30d / 90dperiod selector. The control was decorative —useDashboardStats()ignored the value and every period rendered the same data. Documented as out-of-scope §6.2; re-introduce whenGET /api/client/dashboard/statsaccepts?days=Nand the repository plumbs the value through to its date-range queries.spec-026Avatar regression fix:<Avatar>now setsunoptimized={true}for any externalhttp(s)://URL so OAuth-provider hostnames not innext.config.ts > images.remotePatternsno longer fall through to the gradient initials. Also dropped the unconditionalpriorityprop. Avatars are 32–48 px, so optimization buys nothing.spec-026Replaced thet(label).split(' ').slice(-1)[0]last-word hack in<DashboardMobileSummary>with dedicatedclient.dashboard.STATS.*_SHORTkeys. The trick produced broken labels in Russian, Arabic, Chinese, and any language where the meaningful word isn't the last token.spec-026Backfilled 30 new dashboard root keys + 4STATS.*_SHORTkeys across all 20 non-English locale files (ar,bg,de,es,fr,he,hi,id,it,ja,ko,nl,pl,pt,ru,th,tr,uk,vi,zh) with real translations (no English-identical entries). Backfill script used once and removed.