feat(console): let an administrator set a guild up without leaving the browser - #154
Open
TheMeinerLP wants to merge 1 commit into
Open
feat(console): let an administrator set a guild up without leaving the browser#154TheMeinerLP wants to merge 1 commit into
TheMeinerLP wants to merge 1 commit into
Conversation
…e browser PR #149 shipped the routes and nothing rendered them: setting a guild up meant running /setup in Discord or calling three endpoints by hand. This is Server Setup, first entry of the Admin View, walking the invitation, the server, what to record, and what came of it. The console writes an intent rather than configuration -- api holds no Discord token and never will -- so the page's job is to express three properties of that mechanism that the payload does not state on its own. A failure is terminal, so the page says there is no retry and offers another ask instead of a wait. The newest ask wins, so a superseded request is never drawn in the failure colour and a request that is not the one this browser sent says so. And bot.has_arrived is what separates "this server has no voice channels" from "nobody has looked yet", so the picker draws four states rather than one empty list. Two things reading the applier decided rather than the brief. Setting up adds to voice_channel_ids and never removes from it, so already-recorded channels are ticked and disabled instead of offering a control that would do nothing. And a stored channel the mirror cannot resolve is never carried into a request: the applier refuses a channel it cannot see, one refusal settles the whole intent as failed, and a guild with one deleted room would otherwise have every request it ever made fail over it. Every decision is in app/utils/onboarding.ts and tested there; the page keeps layout, request plumbing and the three-second poll, which stops on settlement, on unmount, and after five minutes of a bot that never arrives.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Server Setup (
/admin/onboarding), the page that drives the three routes #149 shipped. Until now a guild was set up by running/setupin Discord or by callingGET /api/invite,POST /api/guilds/{id}/setupandGET /api/guilds/{id}/setupby hand — the API existed and nothing in the console reached it.What it does
Four steps, in the order somebody actually performs them.
1. Put the bot in the server. The authorize link from
GET /api/invite, opened in a new tab. Whenurlisnull— a deployment with noSTURNUS_DISCORD_CLIENT_ID— the page says that is a configuration fact rather than a fault and hands over the two values the API still sends, which are exactly what somebody ticks in Discord's own URL generator instead. No dead link is ever offered in place of a working one.2. Choose the server. The same guild switcher Bot Settings, Destinations and the Queue use, remembered choice and stored key included, so switching servers on one admin page carries to the others. An empty list is not a dead end here the way it is on every other admin page — it is the ordinary middle of this flow, so it says a server appears once the bot has joined and swept it, and offers a way to look again.
3. Say what to record. A channel picker over the mirror, grouped by kind, plus the consent role's name. Both go into one
POST, which answers 202 with the guild's setup state.4. What came of it. The request panel: a badge word, a heading, the decided sentences under it, who asked and when, what was asked for, and — on a failure only — the bot's own text verbatim and multi-line, because it names which channel, which permission and what to do about it, and no status word this console could invent would say that.
Why it is shaped this way
The console does not write
guild_config.apiholds no Discord token and never will (Spec 13.2), so it writes an intent and the bot's ten-second tick makes it true through the same planner/setupuses. The page's real job is therefore not a form; it is expressing three properties of that mechanism that are invisible in the payload, each of which looks on screen exactly like the state it must not be confused with.A failure is terminal, so the page says so and offers another ask. One attempt settles an intent whichever way it went — the tick runs six times a minute forever, and an intent left unapplied after failing would retry a permission error against Discord's rate limiter just as often. There is nothing running behind a failed request and no back-off to wait out, so the panel says that outright and says that asking again is a new request rather than a resumption. A page that showed "failed" beside a spinner would have somebody waiting on a retry that does not exist.
The newest ask wins, so a superseded request is never red. Nothing went wrong to one: it was replaced before the bot reached it and never acted on. It is rendered in the neutral tone with the word "replaced", and the sentence explains why the newest wins outright — applying both in order would finish on the older list, which is a correction being overwritten by the mistake it corrected. There is a second half to this that only shows up in use:
GETanswers with the guild's newest request, which after a colleague pressed the button thirty seconds later is theirs. The page keeps the id it saw when this browser asked and, when a later poll answers with a different one, says so before anything else on the panel. Reading somebody else's applied request as your own is how an administrator concludes their channel list is in force when another one is.pendingpast a tick means the bot is not there, andbot.has_arrivedsays which. The picker draws four states, not one empty list: nobody has looked yet while nothing has been mirrored, this server has no voice channels once something has, the names could not be read when the directory call failed, and the picker itself. The first two are one empty array on the wire and opposite instructions on screen — one says wait ten seconds, the other says go and create a channel — andhas_arrivedis asked before the directory failure, because a directory that answered nothing for a guild nothing has been mirrored for answered correctly. A pending request in that state gets its own tone and its own sentence rather than a slower spinner.Where the role-position caveat lives. Manage Roles in the invitation is not sufficient by itself: the bot's own role must sit above the consent role in Server Settings → Roles or Discord refuses the edit, and no bitmask expresses that. It sits under step 1, marked, because it is a precondition and the drag can be done in the same visit to Discord that the invitation already requires. It is not repeated as a diagnosis on every failure, because
request.erroralready carries the bot's own words — which name exactly that when it is the cause — and a fixed sentence pinned under every failure would be noise over the one place a person should be reading.Two decisions that came from the applier rather than from the API's shape. A setup request adds to
voice_channel_idsand never removes from it, so unticking a channel this server already records would do nothing at all; those rows are ticked, disabled, and labelled in a word, with one sentence pointing atvoice_channel_idson Bot Settings, which is where removal actually lives. And a stored channel the mirror cannot resolve is deliberately left out of every request:apply_setup_intentsrefuses a channel it cannot see, that refusal is aproblem, and one problem settles the whole intent asfailed— so a guild with a single deleted room in its configured list would otherwise have every request it ever made fail over a channel nobody asked about. The page names those ids and says what to do with them instead.What it deliberately refuses to do
UiSelect; the grouping, the kind headings and the bare-id fallback are~/utils/directory's, which is where this console already decided how a snowflake with no row is presented.status === 'pending', because a page that re-reads itself every three seconds and keys a skeleton off the status alone erases what somebody is reading, four times a minute.Colour is never the only thing that says a state
Every tone carries a badge word — waiting, not started, done, failed, replaced — and an outcome this build has never heard of renders as itself rather than being narrowed away, because
outcomeis text and not a database enum precisely so a newer bot's word is a row a reader can ignore. The two states that change on their own arearia-live="polite", the already-recorded rows say so in words as well as by being disabled, and the picker's channels are checkboxes infieldsets under kind legends.Translation
admin.onboarding.*, 84 keys, complete in both languages from the start — nothing on this page was ever hard-coded English, so the sweep that converts the older admin pages has nothing to reach here.nav.onboardingis the one key outside the namespace.i18n/README.mdgains the namespace row and a note on why three of its sentences render the API's own prose beside a translated sentence rather than inside one.docs/operations.md§6.2.14 gains a pointer to the page; the section itself is unchanged, since it is the contract this was built against.Where Server Setup sits in the navigation
First in the Admin View, ahead of Bot Settings, which breaks that list's stated frequency ordering on purpose. Everything else there is read by somebody who already has a working server; this page is read by somebody who has none, and it is the prerequisite of every other entry — there is no configuration, no consent roster, no queue and no report until a server has been set up. An entry needed exactly once, by the person who knows this console least, cannot be fifth in a list of five. The ordering test's comment is amended to say so rather than left to disagree with the code.
One thing worth flagging
The brief described
channelNamingas the console's single answer to how an unresolved snowflake is presented. That function (in~/utils/recordings) is about a recording session's channel; the picker-side answer isresolveChoice/channelChoicesin~/utils/directory, and that is what this page reuses. No second answer to either question was written.Checks
Measured on
eed19d7(v0.16.0) before and on this branch after, inconsole/:vitest run1784 passed in 58 files (was 1719 in 56 — 65 new: 47 inonboarding.spec.ts, 16 inadminOnboardingPage.spec.ts, and 2 that the new.vuefile adds to the per-component sweeps instylesheets.spec.tsandmotion.spec.ts, both of which it passes) ·eslint .clean ·nuxt typecheckclean ·nuxt buildclean — all four green before and after.