Admin is not just another tab
The last 24 hours in Nima Project concentrated on three repositories and one shared decision:
2026-06-13 07:49 +0200-Add generic product admin modules2026-06-13 07:49 +0200-Document generic product admin modules2026-06-13 08:02 +0200-Add Ultimate Porra settings UI2026-06-13 08:03 +0200-Document product admin UI overlays2026-06-13 08:30 +0200-Add Porra admin domain setup script2026-06-13 08:30 +0200-Document Ultimate Porra admin domain2026-06-13 10:18 +0200-Remove public Porra admin and fix scoring rules2026-06-13 10:18 +0200-Document Ultimate Porra admin boundary
The quick reading would be: the admin panel moved elsewhere and some scoring rules were adjusted.
The correct reading is different: Ultimate Porra stopped treating administration as a hidden tab inside the public app and started treating it as a separate product surface.
The important chat line was not technical; it was a boundary
The conversation that best summarizes this stretch was short:
I want it under an admin subdomain
That sounds like a DNS instruction.
It was not.
It was a boundary decision:
- the public pool app is one thing,
- the operational backoffice is another,
- and sharing a repo or components does not mean sharing the same surface or URL.
That distinction matters because the admin area is no longer about decorative settings. It now holds:
- favorites that invalidate vote options,
- official results,
- operational product resources,
- and actions with global impact across all pools.
Once that is true, hiding it behind an admin hash in the same frontend stops being clean. It becomes a risky blend of contexts.
The first move was not Porra-specific; it was platform work
The Add generic product admin modules commit in nima-system is the architectural pivot.
It does not build a one-off panel for one product. It builds a reusable base:
| Piece | What it enables | Why it matters |
|---|---|---|
productAdminModules |
discover admin modules per product | avoids hardcoding a different admin for every app |
| admin resources | read and edit product payloads | separates UI surface from server-side adapters |
| admin actions | run declared operations | avoids sprinkling ad hoc buttons everywhere |
| product-specific overlay | custom UI only where needed | allows specialization without breaking the generic layer |
That order matters.
If the team had built a one-off ultimate-porra screen first, it would have solved an immediate need.
By doing it the other way around, the immediate need got solved without giving up a platform pattern.
The second move was specific: Porra still needed its own UI
Once the generic layer existed, Add Ultimate Porra settings UI followed.
And this leaves a useful lesson: abstraction did not remove the need for a concrete interface.
The competition-settings resource for Ultimate Porra needed:
- a competition selector,
- favorite-team exclusion for
surpriseTeam, - official general-result fields,
- saving through the generic admin resource route,
- and the advanced JSON editor as fallback.
That creates a healthy split between two levels:
The rule here is not "everything must be generic".
The better rule is: the generic layer should carry specialization, not block it.
The strongest diff was not what got added. It was what got removed
The most expressive commit of the day was Remove public Porra admin and fix scoring rules.
In world-cup-porra, the main story was not dozens of new components. It was deletion.
The public app lost:
- the embedded admin route,
- admin navigation,
- embedded admin renderers,
- admin autosaves and admin notifications inside the public frontend,
- and several official-result modules that no longer belonged there.
The commit balance says it clearly: roughly 1900 lines removed against fewer than 200 lines added.
That does not sound like expansion. It sounds like boundary correction.
And the product README states it plainly:
- before: there was an admin screen visible to certain users,
- now:
no admin panel in the user frontend; management lives in Nima Admin Console.
The technical improvement here was not "cleaning code".
It was stopping the product from pretending that the user app can also be the operations console.
The domain was not branding; it was operational meaning
After separating the surface, the subdomain arrived:
admin.<product-domain>
Again, that can look cosmetic.
It is not.
A dedicated domain makes several truths visible at once:
| Signal | What it communicates |
|---|---|
| product domain | public and user-facing experience |
| admin subdomain | operations, configuration and product management |
| separate Pages project | decoupled deployment and decoupled risk |
The chats around this step made it explicit: DNS alone was not enough.
The work also had to:
- deploy the current admin console build,
- attach the custom domain to the admin Pages project,
- validate the Pages state,
- and leave behind script plus documentation so the flow can be repeated without improvisation.
In other words: the subdomain was not a pretty alias. It was the concrete form of a boundary already chosen in product and code.
Scoring changed in the same commit for a related reason
It may look strange that the same commit that removes admin from the public app also adjusts scoring.
In practice, it follows the same discipline.
The correction was precise: once an exact score is present, goal difference and clean sheet are already implied by that score and should not be counted again as separate bonuses.
That changed both totals and tests:
- the
cleanSheetbonus no longer stacks on top of an exact score, - the count of correct answers changes,
- and the rules documentation had to be rewritten so the interface would not promise inconsistent scoring.
This is not a side detail.
It is the same principle at another layer: a poorly defined boundary creates double readings.
Just as the public app should not pretend to be admin, an exact score should not get paid twice for information it already contains.
The documentation was part of the change, not an epilogue
The root repo did not only receive support notes.
It received the pieces that lock the model in place:
- generic admin modules,
- product-specific UI overlays,
- the Ultimate Porra admin domain,
- and a dedicated
ADMIN_BOUNDARY.md.
That matters because the documentation did not arrive "afterwards to explain".
It arrived to close contracts:
- what the public app may do,
- what the admin console must do,
- where each responsibility lives,
- and which routes or actions are no longer allowed.
If that is not written down, two iterations later someone puts an operational button back into the user app "because the fetch already exists".
The central idea: an operations console is a different surface
These 24 hours made the level shift clear:
| Before | Now |
|---|---|
| admin embedded in the public frontend | admin separated into Nima Admin Console |
| admin hash inside the app | admin subdomain |
| admin behavior mixed with user UX | resources, actions and overlays with their own contract |
| product-specific behavior hardcoded in place | reusable generic pattern for future products |
The initial temptation in small products is almost always the same:
"since it is only us, we can just add an admin tab".
That works for a while.
Until that tab:
- changes global data,
- needs different permissions,
- requires a separate deployment,
- or starts behaving more like a console than a user screen.
At that point it is no longer a tab.
It is a second small product attached to the first one.
And if you keep treating it as if it were not, you pay for the confusion in security, UX, deployment and maintenance.
What I am taking from this stretch
- A hidden admin tab inside a public app can be a valid shortcut early on, but it stops being one once it touches global data and product-specific permissions.
- The best product specialization usually stands on a generic base, not on one-off branches.
- An admin subdomain is not only branding: it makes an operational boundary visible.
- Removing
1900lines can be a bigger step forward than adding a new screen. - The same boundary discipline applies to UI and rules: if something is already implicit, it should not be rendered or scored twice.
These were not 24 hours of "moving a screen".
They were 24 hours in which ultimate-porra accepted something important: the backoffice is not just another section of the public experience. It is a different surface, with different risk, different semantics and a different contract.
Part of my product and platform notes. Follow the blog or contact me.