From 9674ede2c4af0c3793824a65edb02dcc99ff57fa Mon Sep 17 00:00:00 2001 From: DIZAMU <117295462+DIZAMU@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:07:52 +0100 Subject: [PATCH] fix(web): steady inbox start and runtime-aware intro --- internal/server/api.go | 3 ++- locales/en.json | 2 ++ locales/no.json | 2 ++ web/src/App.tsx | 2 +- web/src/api.ts | 1 + web/src/shell/Intro.tsx | 10 ++++++---- web/src/views/Inbox.tsx | 25 +++++++++++++++++-------- web/src/views/start.tsx | 12 +++++++----- 8 files changed, 38 insertions(+), 19 deletions(-) diff --git a/internal/server/api.go b/internal/server/api.go index e4c9531..84876be 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -87,6 +87,7 @@ func decode(r *http.Request, v any) error { type overviewJSON struct { Lang string `json:"lang"` + Terminal bool `json:"terminal"` // the dashboard has a web terminal (v2) Active *activeJSON `json:"active"` Starting *startingJSON `json:"starting"` // a start in progress; null if none Next *nextJSON `json:"next"` @@ -148,7 +149,7 @@ type statsJSON struct { func (s *Server) handleOverview(w http.ResponseWriter, r *http.Request) { ctx := r.Context() lang := s.lang(ctx) - out := overviewJSON{Lang: lang, Levels: []levelJSON{}} + out := overviewJSON{Lang: lang, Terminal: s.cfg.Engine.HasTerminal(), Levels: []levelJSON{}} s.startMu.Lock() if s.starting != nil { st := *s.starting diff --git a/locales/en.json b/locales/en.json index 5eba303..ef7d583 100644 --- a/locales/en.json +++ b/locales/en.json @@ -340,6 +340,8 @@ "web.intro.inbox.body": "Each ticket is a Linux server with a fault. The one at the top is picked for you: open \"Why this ticket\" to see why.", "web.intro.session.title": "Ticket left, terminal right", "web.intro.session.body": "Opening a ticket starts a sandbox. Read the ticket, find and fix the fault in the terminal, then run the check (Ctrl+Enter).", + "web.intro.session_shell.title": "Ticket left, shell beside it", + "web.intro.session_shell.body": "Opening a ticket starts a sandbox. Read the ticket, run rootcause shell in a terminal to get onto the machine, find and fix the fault, then run the check (Ctrl+Enter).", "web.intro.hints.title": "Hints and stars", "web.intro.hints.body": "Stuck? Reveal a hint. A solve with no hints earns 3 stars, with one or two hints 2, and with all three 1. After the third hint you can give up and read the walkthrough.", "web.intro.levels.title": "Levels and stats", diff --git a/locales/no.json b/locales/no.json index 42425b5..a8f3aab 100644 --- a/locales/no.json +++ b/locales/no.json @@ -340,6 +340,8 @@ "web.intro.inbox.body": "Hver sak er en Linux-server med en feil. Den øverste er valgt for deg: åpne «Hvorfor denne saken» for å se hvorfor.", "web.intro.session.title": "Saken til venstre, terminalen til høyre", "web.intro.session.body": "Når du åpner en sak, starter en sandkasse. Les saken, finn og rett feilen i terminalen, og kjør sjekken (Ctrl+Enter).", + "web.intro.session_shell.title": "Saken her, skallet ved siden av", + "web.intro.session_shell.body": "Når du åpner en sak, starter en sandkasse. Les saken, kjør rootcause shell i en terminal for å komme inn på maskinen, finn og rett feilen, og kjør sjekken (Ctrl+Enter).", "web.intro.hints.title": "Hint og stjerner", "web.intro.hints.body": "Står du fast? Vis et hint. Løser du uten hint, får du 3 stjerner, med ett eller to hint 2 og med alle tre 1. Etter det tredje hintet kan du gi opp og lese gjennomgangen.", "web.intro.levels.title": "Nivåer og statistikk", diff --git a/web/src/App.tsx b/web/src/App.tsx index c48b22a..0196606 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -183,7 +183,7 @@ export function App() { setPrefs({ introDone: false }); }} /> - setPrefs({ introDone: true })} /> + setPrefs({ introDone: true })} /> setPalette(false)} overview={overview.data} actions={paletteActions} /> diff --git a/web/src/api.ts b/web/src/api.ts index 46ef8c2..3d2aebe 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -9,6 +9,7 @@ export type Strings = Record; export interface Overview { lang: Lang; + terminal: boolean; // the dashboard has a web terminal (v2) active: { id: string; exercise: string } | null; // A session start in progress: the engine's generic step, nothing more. starting: { exercise: string; step: number; steps: number } | null; diff --git a/web/src/shell/Intro.tsx b/web/src/shell/Intro.tsx index c9af50f..f71c077 100644 --- a/web/src/shell/Intro.tsx +++ b/web/src/shell/Intro.tsx @@ -9,8 +9,9 @@ type Step = (typeof steps)[number]; // Intro is the guided first-run tour: four steps in a modal dialog, with // Back, Next and Skip. It opens on a browser's first visit (no stored // preferences) and again from the help dialog. Esc, Skip and the last step's -// button all end it, and end calls onDone. -export function Intro({ open, onDone }: { open: boolean; onDone: () => void }) { +// button all end it, and end calls onDone. Without a web terminal (v1) the +// session step points to rootcause shell instead. +export function Intro({ open, terminal, onDone }: { open: boolean; terminal: boolean; onDone: () => void }) { const { t } = useI18n(); const [index, setIndex] = useState(0); const next = useRef(null); @@ -29,6 +30,7 @@ export function Intro({ open, onDone }: { open: boolean; onDone: () => void }) { moved.current = false; }, [index]); const step = steps[index] ?? "inbox"; + const text = step === "session" && !terminal ? "session_shell" : step; const last = index === steps.length - 1; const go = (i: number) => { moved.current = true; @@ -44,8 +46,8 @@ export function Intro({ open, onDone }: { open: boolean; onDone: () => void }) {

{t("web.intro.step", { n: index + 1, total: steps.length })}

-

{t(`web.intro.${step}.title`)}

-

{t(`web.intro.${step}.body`)}

+

{t(`web.intro.${text}.title`)}

+

{t(`web.intro.${text}.body`)}

- {next && } + {next && !starting && } {starting && } ); diff --git a/web/src/views/start.tsx b/web/src/views/start.tsx index 9075338..f3784bf 100644 --- a/web/src/views/start.tsx +++ b/web/src/views/start.tsx @@ -7,16 +7,18 @@ import { ProgressSteps, useAnnounce } from "../ui/kit"; // stepKeys name the engine's start steps (session.StartStep), in order. const stepKeys = ["web.start.step.image", "web.start.step.machine", "web.start.step.setup", "web.start.step.fault"]; -// useStart starts a session ("" = the recommended one) and opens it. While -// it runs, onStarting(true) makes the app poll the overview fast, so the -// start steps tick off. +// useStart starts a session ("" = the recommended one) and opens it. busy is +// the row that was clicked (row, or the exercise), never re-derived from the +// recommendation, which moves once the start creates the session. While it +// runs, onStarting(true) makes the app poll the overview fast, so the start +// steps tick off. export function useStart(reloadOverview: () => void, onStarting: (on: boolean) => void) { const announce = useAnnounce(); const { t } = useI18n(); const [busy, setBusy] = useState(null); const [error, setError] = useState(""); - const start = (exercise: string) => { - setBusy(exercise); + const start = (exercise: string, row = exercise) => { + setBusy(row); setError(""); onStarting(true); announce(t("web.start.announce"));