Repository navigation
v2.1.0
v2.1.0
- Released: 2026-04-27
- Summary: Managed accounts, SEO primitives, premium-gate primitives, sanitized markdown messages, bug-report flow, self-service account deletion, redesigned landing page, preview deployments, and Claude UI-check tooling.
Upgrade Notes
No breaking changes. Most new features are off by default behind feature flags or env vars and won't change behavior until a sub-app opts in.
Migration
A single new migration: app/backend/migrations/2026-04-26-v2.1.0-managed-accounts.sql. It:
- Drops
NOT NULLfromusers.email,first_name,last_name,hashed_password(managed accounts have none of these). - Adds
auth_method,display_name,managed_account_group_idtousers, plus ausers_auth_integrityCHECK constraint that requires existing rows be one ofpassword/oauth/access_code/deleted. - Backfills
auth_method='oauth'for rows withoauth_provider IS NOT NULL; everything else stayspassword. - Creates
managed_account_groupsandaccess_codes.
All existing rows pass the CHECK with the default backfill. The migration is irreversible once any user has auth_method='access_code' or 'deleted'.
New environment variables
Add to your .env (the templates are already in .env.template):
# Required for the dev-mint-token endpoint. Leave empty to keep the route
# unregistered in production-like envs.
INTERNAL_API_KEY=`openssl rand -hex 32`
# Managed accounts (off by default).
MANAGED_ACCOUNTS_ENABLED=false
MANAGED_ACCOUNT_GROUP_MAX_PER_USER=20
MANAGED_ACCOUNTS_MAX_PER_USER=50MANAGED_ACCOUNTS_ENABLED is forwarded to the frontend as NUXT_PUBLIC_MANAGED_ACCOUNTS_ENABLED automatically by docker-compose.
New Features
Managed accounts
Email-less child accounts that sign in by short access code instead of email/password. An owner provisions a group, bulk-imports accounts into it, and shares a public picker URL (/c/:token) where a managed user picks their name and types their code. Premium status is inherited from the group owner via is_premium_effective(session, db_user).
- New schemas:
managed_account_groups,access_codes, plusauth_method/display_name/managed_account_group_idonusers, with a CHECK constraint enforcing one ofpassword/oauth/access_code/deleted. - Pages:
/managed-account-groups/index,/managed-account-groups/[id], public/c/[token]picker. - New middleware:
managed-accounts-enabled.ts,nonmanaged-only.ts. - Pluggable
can_manage_groupspolicy (app/backend/src/helpers/policies.py) so apps can override who is allowed to provision groups. - Composables:
useAccessCode,useManagedAccountGroup,usePickerLink,useUserDisplay.
SEO primitives
@nuxtjs/sitemapregistered with explicit URLs (auto-discovery off so private routes can't leak).- Six schema.org JSON-LD helpers (Course, FAQPage, Product, LocalBusiness, BreadcrumbList, Article) with pure builders and vitest coverage (
tests/schema.test.ts). <Breadcrumb>component wired throughuseOverridableso apps can swap visuals without losing SEO.useCanonical()composable and a dynamic/robots.txtroute.- Sub-app extension points:
config/sitemap-ext.ts,config/robots-ext.ts(templates). PUBLIC_URLis now bound through to the frontend container; theserver/directory is mounted in dev so nitro routes load.
Markup-driven premium gating
Frontend primitives for premium teasers — server-side entitlement still belongs in require_premium (see CLAUDE.md):
<PremiumGate>with#teaserand#gatedslots. Non-premium users gethidden/aria-hidden/inertplus schema.org JSON-LD (Article +isAccessibleForFree=false) for Google flexible sampling. The CTA opens the existingModalAuthfor logged-out users instead of bouncing through/login.<PremiumBadge>decorative lock chip.usePremiumStatus()for cheap entitlement reads with auto-refresh on tab focus.
Demo wired into the landing-page bento.
Sanitized markdown for conversation messages
User and admin message pages now have a Write/Preview compose toggle backed by markdown-it + a private DOMPurify instance. The renderer refuses to run on the server so DOMPurify can't fail-open without a DOM, and the link-safety hook is scoped to its own purifier so target="_blank" doesn't leak to other DOMPurify callers.
Bug-report entry point
Drop-in <BugReportButton> opens a modal that posts a support conversation with subtype=bug_report, reusing the existing message system end-to-end (admin inbox, threading, status).
CoreConversationSubtypeenum +UserInitiableSubtypealias inconstants_ext.py:template, so sub-apps widen the accepted set withtype UserInitiableSubtype = CoreConversationSubtype | Literal[...].- Per-subtype rate limits (5/hr + 20/day) and a 10-minute account-age gate on
POST /conversations. - Server-side URL stripping on the bug-report body for PII hygiene.
- Shared
<ConversationComposerModal>extracted from/messages.
Self-service account deletion
/account gets a Privacy tab; users can request deletion and confirm via emailed link. The /delete-account page renders the confirmation. Backend wires up helpers/account_deletion.py, dedicated email templates, and matching auth-token plumbing.
Landing page redesign
Asymmetric hero (headline + CTAs left, live terminal panel right) and a bento features grid. The terminal's last line is bound to the existing API health probe and flips between probing / healthy / unreachable. New i18n keys live in app-specific en.json/fr.json so the starterpack-managed core locale files stay untouched.
Lazy YouTube facade
<YouTubeFacade> renders a thumbnail + play button and only loads the real iframe on click — saves ~500KB of JS per unplayed video. Uses youtube-nocookie.com so no YouTube cookies are set until the user presses play.
Preview deployments
docker-compose.preview.yml + .github/workflows/preview.yml spin up an isolated stack per pull request and post the preview URL as a PR comment. The comment is now deduped via a hidden marker so reruns update the same comment instead of spamming.
Internal mint-token endpoint (dev tooling)
POST /api/v1/internal/mint-token returns an access token for any user. Gated by INTERNAL_API_KEY — when the key is empty the route is unregistered, so production envs that don't set it are unaffected.
Used by npm run dev-mint-token, which prints a JS snippet that injects the token into a Playwright browser session. With no email argument, it falls back to the first non-deleted admin. Lets Claude run UI checks on auth-gated pages without going through the login form.
Also adds .mcp.json (Playwright MCP server) and ignores .playwright-mcp/.
Bug Fixes
- Managed-account schemas weren't picked up by
convert-models, soManagedAccount{Group}*andPublicPicker*types were missing on a fresh install (local typecheck happened to pass on cached types; CI failed). Now in theCoreModelsunion. - Preview-URL PR comments are deduped via a hidden marker — reruns update the existing comment instead of posting a new one.
New Core Files
Backend
app/backend/migrations/2026-04-26-v2.1.0-managed-accounts.sqlapp/backend/src/constants_ext.py:templateapp/backend/src/controllers/internal.pyapp/backend/src/controllers/managed_account_groups.pyapp/backend/src/crud/access_codes.pyapp/backend/src/crud/managed_account_groups.pyapp/backend/src/helpers/access_codes.pyapp/backend/src/helpers/account.pyapp/backend/src/helpers/account_deletion.pyapp/backend/src/helpers/bug_report.pyapp/backend/src/helpers/policies.pyapp/backend/src/helpers/user_display.pyapp/backend/src/models/access_code.pyapp/backend/src/models/managed_account_group.pyapp/backend/src/schemas/managed_account.pyapp/backend/src/templates/email/account_deletion.htmlapp/backend/src/templates/email/account_deletion.txtapp/backend/tests/test_auth_code.pyapp/backend/tests/test_bug_report.pyapp/backend/tests/test_managed_account_groups.pyapp/backend/tests/test_managed_account_permissions.pyapp/backend/tests/test_user_display.py
Frontend
app/frontend/components/account/PrivacyTab.vueapp/frontend/components/common/Breadcrumb.vueapp/frontend/components/common/BreadcrumbDefault.vueapp/frontend/components/common/BugReportButton.vueapp/frontend/components/common/PremiumBadge.vueapp/frontend/components/common/PremiumGate.vueapp/frontend/components/common/YouTubeFacade.vueapp/frontend/components/managed-accounts/BulkImportModal.vueapp/frontend/components/messages/ConversationComposerModal.vueapp/frontend/config/robots-ext.ts:templateapp/frontend/config/sitemap-ext.ts:templateapp/frontend/middleware/managed-accounts-enabled.tsapp/frontend/middleware/nonmanaged-only.tsapp/frontend/pages/c/[token].vueapp/frontend/pages/delete-account.vueapp/frontend/pages/managed-account-groups/index.vueapp/frontend/pages/managed-account-groups/[id].vueapp/frontend/server/routes/robots.txt.tsapp/frontend/e2e/seo.spec.tsapp/frontend/tests/markdown.test.tsapp/frontend/tests/schema.test.tsapp/frontend/tests/useBreadcrumb.test.tsapp/frontend/tests/useCanonical.test.ts
Infra & tooling
docker-compose.preview.yml.github/workflows/preview.yml(covered by.github/workflows/*glob)scripts/_core/dev-mint-token.sh(covered byscripts/_core/*glob)