Quizzja screen atlas · 10 of 10 · CMS — Next.js 16 · apps/cms
CMS — the operator side
The admin app at cms.quizzja.com: a Next.js BFF over the same ConnectRPC AdminService, with its own design system — purple on lilac, the same press-down shadows. This is where puzzles are generated, reviewed and scheduled, where the lexicon and the store catalogue are kept, and where player feedback is triaged. Role permissions decide which nav entries render at all.
Sign in /login · app/login/page.tsx The only unauthenticated page. Credentials are checked against an enabled operator account, not a player account — a valid Quizzja player who is not an operator is refused with the same message as a wrong password.
Dashboard
Everything generated, reviewed and published, at a glance
NInikolay
48
Generation jobs
41 succeeded
6
Puzzle drafts
Awaiting review
7 / 8
Daily auto-generation
Languages on
132
Activity events
Recent

Recent activity

View all
Generation job 4f21 finished — 12 drafts for Englishgeneration.job.succeeded
Daily published for Bulgarian · 2026-08-22daily.published
Draft “Saturday Standard” approved by nikolaypuzzle.approved
1,480 English words imported from lexgenwords.imported
Generation job 9ab0 running — German 13×13generation.job.running
Daily published for German · 2026-08-22daily.published
“Weekend Cryptic” is livepuzzle.published

Statistics

Jobs by status
Succeeded41
Running2
Queued3
Failed2
Dashboard / · app/(app)/page.tsx The operator landing page: four counters over the generation pipeline, the live activity feed, and jobs broken down by status. Every number is a ConnectRPC query against the same AdminService the rest of the CMS uses.
Generate
Queue a generation run against the Go generator
NInikolay

Generate crosswords

Runs on the Go generator over gRPC/h2c

Queued job 9ab0 · 12 drafts requested for English

Last five runs

JobLanguageSizeStatusDrafts
9ab0…German13×13Running4 / 12
4f21…English11×11Succeeded12
31c7…Bulgarian9×9Succeeded8
2d90…Spanish15×15Failed0
1a55…French11×11Queued
Generate /generate · app/(app)/generate/page.tsx Where puzzles come from. The form queues a River job that the worker hands to the Go generator over gRPC/h2c; the run fills grids by constraint propagation and writes drafts, never published puzzles. Seed is optional and makes a run reproducible.
Daily
Which language has a crossword on which day
NInikolay
5 / 8
Languages live today
2
Gaps to fill today
7 / 8
Languages on auto
7
Day window

17 – 23 August 2026

Published Not published — click to generate Auto-generation off
Mon 17Tue 18Wed 19Thu 20Fri 21Sat 22Sun 23English Auto+Bulgarian Auto+German Auto++Spanish Auto+++French Auto+Japanese PausedPolish Auto+++Portuguese Auto+

Future slots appear as not published because the API exposes no forward schedule. A daily generated for a later date becomes visible on that date.

Daily /daily · app/(app)/daily/page.tsx The seven-day schedule as a grid of language rows. Green is published, a red outline is a gap you can click to generate on the spot, grey means auto-generation is off for that language. This is the screen that keeps Today from being empty in any market.
Catalog
Packs, groups and in-app purchase products
NInikolay

Category (pack)

Group

IAP product

Published products

IDTypeStoreSKUPriceGrant
com.quizzja.hints10HintsApplecom.quizzja.hints10USD 1.9910
com.quizzja.hints50HintsApplecom.quizzja.hints50USD 4.9950
com.quizzja.noadsRemove adsApplecom.quizzja.noadsUSD 2.99
com.quizzja.premium.monthlySubscriptionApplecom.quizzja.premium.monthlyUSD 3.99 / month
science-miniPackStripeprice_1Q…EUR 2.99
Catalog /catalog · app/(app)/catalog/page.tsx Three editors and one table: the packs players browse, the groups that organise them, and the IAP products behind Pro, Remove ads, hint packs and premium packs — with the per-store SKU each one maps to. The players' store catalogue is this table.
Lexicon
Word and clue stock per content language
NInikolay

Content health

Usable means enabled and carrying at least one clue — the pool generation draws from.
Largest grid every launched language can fill9×9
LanguageWordsEnabledUsableClue coverageCluesApprovalLargest grid
English en41,20838,94036,11293%34,880Every clue approved15×15
Bulgarian bg22,47021,00619,88494%19,112118 pending13×13
German de28,11526,33024,90295%24,11042 pending15×15
Spanish es19,66018,00414,22079%13,980310 pending11×11
French fr21,33020,11818,66093%18,220Every clue approved13×13
Japanese ja9,8448,1125,00662%4,980520 pending9×9
Polish pl6,2205,8803,11053%3,002201 pending9×9
Portuguese pt17,90516,74015,22091%14,98076 pending13×13

2 of 8 launched languages cannot fill an 11×11. Affected languages: Japanese, Polish. Generation at those sizes has no word to place.

Lexicon /lexicon · app/(app)/lexicon/page.tsx The constraint that decides what can be generated at all: usable words — enabled and carrying a clue — per language and per word length. The verdict line names the largest grid every launched language can fill, and which languages fall short of it.
Translations
Interface strings, per locale
NInikolay

Add or edit string

Saved play.reveal

Strings · de

1,204 total · Unapproved only
KeyValueStatus
nav.todayToday✓ approved
nav.puzzlesPuzzles✓ approved
today.greetingReady to play?✓ approved
today.chipTODAY'S CROSSWORD✓ approved
play.revealRevealPending
play.revealAllTitleReveal the whole solution?Pending
pro.upgradeUpgrade to Pro✓ approved
store.tryFreeTry 7 days freePending
Translations /translations · app/(app)/translations/page.tsx The ICU message catalogue behind every client, editable per locale with an approval flag. Source strings live in i18n/source/en.json and travel through Crowdin; this screen is for fixing one string without a round trip.
Generation jobs
Live · auto-refreshing
NInikolay

Generation jobs

Live · auto-refreshing
JobStatusProgressDraftsMessage
9ab0…
German 13×13
Running
4 / 12fill 42% · 00:38 elapsed
4f21…
English 11×11
Succeeded
12completed in 01:12
31c7…
Bulgarian 9×9
Succeeded
8completed in 00:41
2d90…
Spanish 15×15
Failed
0no usable word of length 15
1a55…
French 11×11
Queued
waiting for a generator slot
0e83…
Japanese 9×9
Succeeded
6completed in 00:55
Generation jobs /jobs · app/(app)/jobs/page.tsx Every generation run, refreshing while any is in flight. A failure reports why in the operator's own terms — the Spanish 15×15 here failed because the lexicon has no usable 15-letter word, which is the Lexicon screen's verdict restated as a job outcome.
Drafts
6 awaiting review
NInikolay
Saturday Standard
English · 11×11 · medium · 38 words · job 4f21 · generated 2 hours ago
1HAYO6AKERERIN738495
SymmetryRotational
Every word has a clue38 / 38
!Duplicate clue1 found
Minimum word length3
ACROSS
1Mown grass, stored dry3
4Sturdy hardwood3
7Downpour4
9Pasture animal that lows3
11Rows of crops5
DOWN
1Farmer's early alarm7
2Autumn harvest4
3Sheep's coat6
5Pasture animal that lows3
6Beehive product5
Generator go/csp · revision r1
Drafts · review queue /drafts · app/(app)/drafts/page.tsx The gate between generation and publication. A draft is selected on the left and reviewed on the right: the rendered grid, the automated quality checks, and both clue lists with duplicates flagged. Approve, Reject and "Publish now" all write to the audit log.
Activity
Events: 132
NInikolay

Activity

Events: 132
AllGenerationDaily
Generation job 4f21 finished — 12 drafts for English
generation.job.succeededSystem2 minutes ago
Daily published for Bulgarian · 2026-08-22
daily.publishedOperator 4a91c214 minutes ago
Draft “Saturday Standard” approved by nikolay
puzzle.approvedOperator 4a91c21 hour ago
1,480 English words imported from lexgen
words.importedSystem3 hours ago
Generation job 9ab0 running — German 13×13
generation.job.runningSystem3 hours ago
Daily published for German · 2026-08-22
daily.publishedSystem5 hours ago
“Weekend Cryptic” is live
puzzle.publishedOperator 4a91c2yesterday
Activity /activity · app/(app)/activity/page.tsx The full event feed the dashboard card samples, filterable by domain. Each event names its actor — the system, or the operator id that caused it — so a surprising change on a live language can be traced back in one step.
Feedback
Player bug reports and feature requests
NInikolay
3
New
31
Bugs
18
Feature requests
44
All reports

Inbox

5 shown
All typesAll statusesClear filters
IDTypeDescriptionReporterSourceStatusPriority
#812Bug reportGrid scrolls under the keyboard on a 5.4-inch iPhonealex@example.comiOS 17.4 · 1.4.0NewHigh
#811Feature requestPlease add a dark theme for late-night solvingWeb · 1.4.0TriagedNormal
#809Bug reportPurchase confirmed but hints not crediteddana@example.comAndroid 14 · 1.3.9In progressUrgent
#804Feature requestLet me print a whole pack as one PDFsam@example.commacOS 14 · 1.4.0PlannedLow
#799Bug reportBulgarian clue text overflows the clue barWeb · 1.3.8ResolvedNormal
Player feedback /feedback · app/(app)/feedback/page.tsx Everything submitted from the players' Report a bug and Request a feature dialogs, mirrored into a private GitHub issue. Triage sets status and priority and can carry an internal note; the screen is read-only without FEEDBACK_WRITE.
Audit log
Entries: 1,904
NInikolay

Operator audit log

Entries: 1,904
TimeOperatorActionEntity
2026-08-22 11:584a91c2puzzle.approvedraft 7c31…
2026-08-22 11:414a91c2daily.generatede · 2026-08-22
2026-08-22 10:164a91c2word.importen · 1,480 rows
2026-08-21 17:02b7e004operator.updateviewer → editor
2026-08-21 16:554a91c2product.savecom.quizzja.hints50
2026-08-21 09:14b7e004puzzle.rejectdraft 22ab…
2026-08-20 14:304a91c2translation.approvede · 84 strings
Audit log /audit · app/(app)/audit/page.tsx Every operator action, filterable by operator id — approvals, rejections, publishes, product and translation edits, and role changes. Visible only with AUDIT_READ; the nav entry is hidden entirely for roles without it.
Operators
Who can sign in, and what they may do
NInikolay

Operators

5 total
IdentityEmailRoleStatus
nikolayoperator@example.comAdministratorEnabledDisable
maria.geditor@example.comEditorEnabledDisable
stefaneditor2@example.comEditorEnabledDisable
reviewer-1reviewer@example.comViewerEnabledDisable
old-internformer@example.comViewerDisabledEnable

Add or update operator

Only administrators can manage operators. Every change here is written to the audit log.

Operators /operators · app/(app)/operators/page.tsx Administrator-only. Operators sign in through OIDC and are matched by SSO subject; the role decides which nav entries even render. Disabling an account is preferred over deleting it, so the audit log keeps pointing at a real identity.