Update the in-app Guide for this session's shipped features

Documents the new race-day Schedule feature (new section + overview
card), staggered-start car call-outs and the per-driver "Done" flag,
the new Settings and Event Manager tab layouts, and the driver
transponder-conflict warning. Also corrects a bullet that had gone
stale: it claimed Open/Free Practice was the only thing that skipped
the start ceremony, which stopped being true once scheduled "Practice"
sessions started getting the full sequence too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-09-11 13:54:42 +02:00
parent ba4ed30b11
commit 2624ce8262
6 changed files with 60 additions and 12 deletions

View File

@@ -1045,7 +1045,14 @@ const TRANSLATIONS = {
"events.detail_active": "aktiva",
"guide.manage_steps_6": "6. Statusbrickorna visar om varje steg är klart, väntar eller behöver åtgärdas. Sammanfattningen till höger visar också varningar om deltagare, lag eller sessioner saknas.",
"guide.manage_steps_7": "7. Klicka på en varning i sammanfattningen för att hoppa direkt till rätt sektion i Hantera i stället för att leta manuellt.",
"guide.manage_steps_8": "8. Stegkorten högst upp fungerar också som snabbhopp. Klicka på Setup, Format, Generering eller Live / resultat för att gå direkt till rätt block.",
"guide.manage_steps_8": "8. Stegkorten högst upp fungerar också som snabbhopp. Klicka på Setup, Format, Generering eller Live / resultat för att gå direkt till rätt block - de byter flik automatiskt om blocket ligger bakom en annan flik.",
"guide.manage_steps_9": "9. Hantera-sidan för race är uppdelad i fliken Deltagare & lag, Format, Live & resultat samt Schema & utskrift, i stället för en lång scroll. Track-/sponsorevent har kvar den enkla, oflikade layouten.",
"guide.schedule_title": "Tidsschema för racedagen",
"guide.card_schedule_blurb": "Sätt en starttid och en paus mellan sessioner, så räknas varje sessions klockslag ut automatiskt.",
"guide.schedule_1": "1. Öppna Hantera -> fliken Schema & utskrift för ett race-event. Sätt starttid och paus mellan sessioner (standard 3 minuter) och klicka Spara schema.",
"guide.schedule_2": "2. Varje sessions planerade klockslag räknas ut i körordning (practice, sedan kval, sedan finaler) utifrån starttiden, sessionens längd/efterlöp och pausen mellan sessioner.",
"guide.schedule_3": "3. Så fort en session faktiskt startar eller avslutas flyttas resten av schemat automatiskt fram till den verkliga tiden - en sen start eller en session som drar över (inklusive paus) skjuter resten av dagen framåt utan att du behöver räkna om manuellt. En session som startade mer än en minut sent märks \"Försenad\".",
"guide.schedule_4": "4. Det planerade klockslaget skrivs också ut på startlistorna (Skriv ut/PDF startlistor) så förare kan se när deras session ungefär börjar.",
"guide.race_wizard_title": "Create Race Wizard",
"guide.race_wizard_1": "1. Börja i Race Setup och använd wizarden när du skapar ett nytt race, i stället för att bygga allt direkt i Hantera.",
"guide.race_wizard_2": "2. Steg 1 sätter namn, datum, klass och preset. Presetet fyller rimliga standardvärden innan du finjusterar något.",
@@ -1696,11 +1703,12 @@ const TRANSLATIONS = {
"guide.audio_1": "1. Öppna Inställningar -> Raceljud och utrop. Välj svenska eller engelska och ställ master-, effekt-, röst- och passing-volym.",
"guide.audio_2": "2. Aktivera effekt och röst separat för varje event. Testknappen på raden provar exakt det eventet; saknade WAV-filer hoppas över eller använder passing-fallback.",
"guide.audio_3": "3. Välj 0, 10, 30 eller 60 sekunders förstart. För race följer sedan Förare gör er redo, startfras, fem startljus, slumpad väntan och startton.",
"guide.audio_4": "4. Den officiella raceklockan startar i samma callback som starttonen. Open/Free Practice startar direkt utan race-sekvens.",
"guide.audio_4": "4. Den officiella raceklockan startar i samma callback som starttonen. Bara Quick Open Practice och fri träning startar direkt utan race-sekvens - en schemalagd \"Practice\"-session i ett race/sponsor-event får samma nedräkning, ljus och startton som kval/heat/final.",
"guide.audio_5": "5. Under Tidtagning finns manuella knappar för Stopp på banan, Banan fri, Röd flagg, felaktig start och banvaktsvarning. Kritiska utrop avbryter lägre prioriterat tal.",
"guide.audio_6": "6. Under Förare och Bilar kan du välja pass_01.wav till pass_08.wav. Om filen saknas används standardljudet; lägg egna korta filer i rc-race-sound-pack/sounds/effects/.",
"guide.audio_7": "7. Personbästa, snabbaste varv, banrekord, ledarbyte, tidsvarningar, depå in/ut, finish och podium hämtas från befintliga timinghändelser.",
"guide.audio_8": "8. Ljudet spelas i operatörens webbläsare. Klicka Testa före racet, kontrollera ljudutgången och håll Tidtagning-fliken öppen.",
"guide.audio_9": "9. Staggered start i ett kvalheat läser upp varje bils transpondernummer i startordning, cirka 3 sekunder mellan varje bil, direkt efter den vanliga starten - mass/position-start läser aldrig upp enskilda bilar.",
"guide.sponsor_title": "Skapa Sponsor Event: 10 personer, 4 bilar",
"guide.sponsor_1": "1. Lägg upp 4 bilar i sidan Bilar med unikt transponder-ID. Lägg gärna också in märke/modell i brandfältet.",
"guide.sponsor_2": "2. Lägg upp 10 förare i sidan Förare. Du kan också spara team/märke i brandfältet och filtrera listan på det senare.",
@@ -1769,6 +1777,8 @@ const TRANSLATIONS = {
"guide.qualifying_3": "Kval-scoring kan kombinera poängläge med poängtabell: placeringstal, fallande efter fältstorlek eller IFMAR 10-9-8-7-6-5-4-3-2-1.",
"guide.qualifying_4": "Tie-break i kvalrankingen kan avgöras på räknade rundor, bästa enskilda varv eller bästa runda/heatresultat.",
"guide.qualifying_5": "Leaderboarden visar seedningsresultat i rätt format, till exempel 3/00:48.321, 3 avg 16.107 eller 3 con 00:49.005.",
"guide.qualifying_6": "Vid staggered start har varje förare sitt eget tidsfönster som börjar vid deras egen första varvpassering - och en grön \"Klar\"-flagga visas bredvid förarens namn i Tidtagning och overlays så fort deras fönster gått ut, även om heatet fortfarande pågår för andra.",
"guide.qualifying_7": "Heatets gemensamma klocka förlängs automatiskt för staggered start så att den täcker hela utropsschemat (antal förare minus ett, gånger sekunderna mellan varje bil) utöver den vanliga längden - annars skulle sista utropade föraren riskera att bli avbruten innan de hunnit köra sin fulla tid.",
"guide.race_day_title": "Race Day och säker start",
"guide.card_race_day_blurb": "Förkontroll, startlista, heat-kö, automatisk backup och fokuserad racedrift.",
"guide.race_day_1": "1. Skapa race och sessioner i Race Setup. Öppna sedan Race Day från gruppen Start.",
@@ -1778,6 +1788,7 @@ const TRANSLATIONS = {
"guide.race_day_5": "5. Vid start synkas state till backend och en konsistent SQLite-backup skapas i data/backups innan den befintliga audio- och starttonssekvensen körs. De 30 senaste sparas.",
"guide.race_day_6": "6. Använd Race Day för start, paus, fortsätt och avslut. Menyn visar server- och sparstatus separat från decodern. Vid sparfel: behåll fliken öppen och klicka Försök igen; väntande ändringar är inte bekräftat sparade på servern. Tidtagning, Domare och Overlay finns som direkta genvägar. Lokal restore kräver stoppad server och npm run backup:restore -- <fil.sqlite> --confirm.",
"guide.race_day_7": "7. Session replay i Tidtagning spelar upp sparade passeringar i ett isolerat testläge. Välj session, ladda och kör i 1x, 2x, 5x eller 10x utan att ändra originalresultat eller träningsstatistik.",
"guide.race_day_8": "8. Redan när du registrerar eller ändrar en förare under Förare varnar systemet om transpondern redan används av en annan förare (t.ex. i fel klass) och namnger vem - ett komplement till förkontrollens kontroll av dubbla transpondrar inom samma heat. Samma person som kör flera klasser med sin egen transponder flaggas aldrig.",
"guide.dashboard_title": "Schemaavvikelse på Översikt",
"guide.dashboard_1": "Översikt visar nu skillnaden mellan planerad tid och faktisk körtid för alla startade sessioner.",
"guide.dashboard_2": "Planerad tid räknar sessionstid plus follow-up. Faktisk tid räknar verklig tid från start till stopp eller nuvarande tid om heatet fortfarande kör.",
@@ -1880,6 +1891,7 @@ const TRANSLATIONS = {
"guide.decoder_roles_12": "12. Exempel: backend 192.168.1.100, main-decoder .10, split .11, depå in .12 och depå ut .13. Decoder-IP-fälten får .10-.13; klientens WS-url använder alltid backend .100 med port 9000-9003.",
"guide.decoder_roles_13": "13. Startordning: pinga decoders från backend, starta backend, starta Managed AMMC, kontrollera AMMC-loggen, använd serverns WS-url och anslut sedan i Tidtagning.",
"guide.decoder_roles_14": "14. Funktionstest: Main ska öka varv, Split ska inte öka varv, Depå in ska visa I DEPÅ och Depå ut ska skapa ett tidtaget stopp. Komplett guide finns i docs/decoder-setup.sv.md.",
"guide.decoder_roles_15": "15. Inställningar är uppdelat i flikarna Anslutning (decoder + Managed AMMC), Ljud, Utseende & overlay och System & backup, i stället för en lång scroll - decoder-inställningarna ovan hittar du under Anslutning.",
"guide.host_1": "1. AMMC körs alltid på samma maskin som backend körs på (`npm.cmd start` i Windows eller `npm start` i Linux).",
"guide.host_2": "2. Om du bara surfar in från en laptop/webbläsare startas ingen process där. Webbläsaren styr bara backend via HTTP.",
"guide.host_3": "3. Kör backend på Linux-servern -> Linux-binären används: `AMMC/linux_x86-64/ammc-amb`.",
@@ -2577,7 +2589,14 @@ const TRANSLATIONS = {
"events.detail_active": "active",
"guide.manage_steps_6": "6. The status badges show whether each step is complete, pending, or needs action. The summary on the right also warns when participants, teams, or sessions are missing.",
"guide.manage_steps_7": "7. Click a warning in the summary to jump straight to the matching Manage section instead of hunting for it manually.",
"guide.manage_steps_8": "8. The step cards at the top also work as quick jumps. Click Setup, Format, Generation or Live / results to move straight to the right block.",
"guide.manage_steps_8": "8. The step cards at the top also work as quick jumps. Click Setup, Format, Generation or Live / results to move straight to the right block - they switch tabs automatically if the block is behind a different tab.",
"guide.manage_steps_9": "9. The race Manage page is split into the Participants & teams, Format, Live & results and Schedule & print tabs instead of one long scroll. Track/sponsor events keep the simple, untabbed layout.",
"guide.schedule_title": "Race day time schedule",
"guide.card_schedule_blurb": "Set a start time and a gap between sessions, and every session's clock time is worked out automatically.",
"guide.schedule_1": "1. Open Manage -> the Schedule & print tab for a race event. Set a start time and the gap between sessions (default 3 minutes) and click Save schedule.",
"guide.schedule_2": "2. Every session's planned clock time is worked out in running order (practice, then qualifying, then finals) from the start time, each session's own duration/follow-up, and the gap between sessions.",
"guide.schedule_3": "3. As soon as a session actually starts or finishes, the rest of the schedule automatically shifts to match reality - a late start or a session that runs long (including any pause) pushes the rest of the day forward with no manual recalculation. A session that started more than a minute late is marked \"Delayed\".",
"guide.schedule_4": "4. The planned clock time is also printed on the start lists (Print/PDF start lists) so drivers can see roughly when their session begins.",
"guide.race_wizard_title": "Create Race Wizard",
"guide.race_wizard_1": "1. Start in Race Setup and use the wizard when creating a new race instead of building everything directly in Manage.",
"guide.race_wizard_2": "2. Step 1 sets name, date, class and preset. The preset fills sensible defaults before you fine-tune anything.",
@@ -3228,11 +3247,12 @@ const TRANSLATIONS = {
"guide.audio_1": "1. Open Settings -> Race sounds and announcements. Select Swedish or English and set master, effects, voice, and passing volume.",
"guide.audio_2": "2. Enable effect and voice separately for every event. Each row has an exact event test; missing WAV files are skipped or use the passing fallback.",
"guide.audio_3": "3. Select a 0, 10, 30, or 60 second pre-start. A race then runs drivers stand by, the start phrase, five start lights, random wait, and start tone.",
"guide.audio_4": "4. The official race clock starts in the same callback as the start tone. Open/Free Practice starts immediately without the race sequence.",
"guide.audio_4": "4. The official race clock starts in the same callback as the start tone. Only Quick Open Practice and free practice start immediately without the race sequence - a scheduled \"Practice\" session inside a race/sponsor event gets the same countdown, lights, and start tone as qualifying/heat/final.",
"guide.audio_5": "5. Timing provides manual controls for Track call, Track clear, Red flag, False start, and Marshal warning. Critical announcements interrupt lower-priority speech.",
"guide.audio_6": "6. Drivers and Cars can select pass_01.wav through pass_08.wav. A missing file uses the default; add short custom files under rc-race-sound-pack/sounds/effects/.",
"guide.audio_7": "7. Personal best, fastest lap, track record, leader changes, time warnings, pit entry/exit, finish, and podium reuse existing timing events.",
"guide.audio_8": "8. Audio plays in the operator browser. Use Test before racing, verify the audio output, and keep the Timing tab open.",
"guide.audio_9": "9. A staggered start in a qualifying heat calls out each car's transponder number in grid order, about 3 seconds apart, right after the official start - a mass/position start never calls out individual cars.",
"guide.sponsor_title": "Create Sponsor Event: 10 drivers, 4 cars",
"guide.sponsor_1": "1. Add 4 cars in Cars with unique transponder IDs. You can also store brand/model in the brand field.",
"guide.sponsor_2": "2. Add 10 drivers in Drivers. You can also store team/brand in the brand field and filter by it later.",
@@ -3301,6 +3321,8 @@ const TRANSLATIONS = {
"guide.qualifying_3": "Qualifying scoring can combine points mode with a points table: placement values, descending by field size or IFMAR 10-9-8-7-6-5-4-3-2-1.",
"guide.qualifying_4": "Qualifying tie-break can now be resolved by counted rounds, best single lap or best round / heat result.",
"guide.qualifying_5": "The leaderboard now shows seeded results in the correct format, for example 3/00:48.321, 3 avg 16.107 or 3 con 00:49.005.",
"guide.qualifying_6": "In a staggered start, each driver's own timing window begins at their own first lap crossing - and a green \"Done\" flag appears next to their name in Timing and overlays as soon as their window has elapsed, even while the heat is still running for others.",
"guide.qualifying_7": "The heat's shared clock automatically extends for a staggered start to cover the full call-out schedule (driver count minus one, times the seconds between each car) on top of the normal duration - otherwise the last-called driver could be cut off before their full time is up.",
"guide.race_day_title": "Race Day and safe start",
"guide.card_race_day_blurb": "Preflight, start list, heat queue, automatic backup, and focused race operation.",
"guide.race_day_1": "1. Create the race and sessions in Race Setup, then open Race Day from the Start group.",
@@ -3310,6 +3332,7 @@ const TRANSLATIONS = {
"guide.race_day_5": "5. Starting synchronizes state to the backend and creates a consistent SQLite backup in data/backups before the existing audio and start-tone sequence runs. The latest 30 are retained.",
"guide.race_day_6": "6. Use Race Day for start, pause, resume, and finish. The menu shows server/save status separately from the decoder. On save failure, keep the tab open and click Retry; pending changes are not confirmed saved on the server. Timing, Judging, and Overlay remain one click away. Local restore requires a stopped server and npm run backup:restore -- <file.sqlite> --confirm.",
"guide.race_day_7": "7. Session replay in Timing replays saved passings in an isolated test mode. Select a session, load it, and run at 1x, 2x, 5x, or 10x without modifying source results or training statistics.",
"guide.race_day_8": "8. Already when you register or edit a driver under Drivers, the system warns if the transponder is already used by a different driver (e.g. in the wrong class) and names who - a complement to the preflight's duplicate-transponder check within a single heat. The same person racing multiple classes on their own transponder is never flagged.",
"guide.dashboard_title": "Schedule drift on overview",
"guide.dashboard_1": "Overview now shows the difference between planned time and actual elapsed time for all started sessions.",
"guide.dashboard_2": "Planned time includes session duration plus follow-up. Actual time uses real time from start to stop, or the current time if the heat is still running.",
@@ -3412,6 +3435,7 @@ const TRANSLATIONS = {
"guide.decoder_roles_12": "12. Example: backend 192.168.1.100, main decoder .10, split .11, pit entry .12, and pit exit .13. Decoder host fields receive .10-.13; client WS URLs always use backend .100 with ports 9000-9003.",
"guide.decoder_roles_13": "13. Start order: ping decoders from backend, start backend, start Managed AMMC, inspect the AMMC log, use server WS URLs, then connect in Timing.",
"guide.decoder_roles_14": "14. Functional test: Main increments laps, Split does not, Pit entry shows IN PIT, and Pit exit creates a timed stop. See docs/decoder-setup.md for the complete guide.",
"guide.decoder_roles_15": "15. Settings is split into the Connection (decoder + Managed AMMC), Audio, Appearance & overlay and System & backup tabs instead of one long scroll - the decoder settings above are under Connection.",
"guide.host_1": "1. AMMC always runs on the backend host (`npm.cmd start` on Windows or `npm start` on Linux).",
"guide.host_2": "2. If you only browse from a laptop/browser, no process is started there. The browser only controls the backend over HTTP.",
"guide.host_3": "3. Run the backend on Linux -> the Linux binary is used: `AMMC/linux_x86-64/ammc-amb`.",

View File

@@ -35,12 +35,13 @@ export function renderGuideView({ dom, t }) {
${renderGuideOverviewCard("guide.training_title", "guide.card_training_blurb", { t })}
${renderGuideOverviewCard("guide.archive_title", "guide.card_archive_blurb", { t })}
${renderGuideOverviewCard("guide.windows_title", "guide.card_decoder_blurb", { t })}
${renderGuideOverviewCard("guide.schedule_title", "guide.card_schedule_blurb", { t })}
</div>
</div>
</section>
<div class="guide-section-grid mt-16">
${renderGuidePanel("guide.race_day_title", ["guide.race_day_1", "guide.race_day_2", "guide.race_day_3", "guide.race_day_4", "guide.race_day_5", "guide.race_day_6", "guide.race_day_7"], "", { t })}
${renderGuidePanel("guide.race_day_title", ["guide.race_day_1", "guide.race_day_2", "guide.race_day_3", "guide.race_day_4", "guide.race_day_5", "guide.race_day_6", "guide.race_day_7", "guide.race_day_8"], "", { t })}
</div>
<div class="guide-section-grid mt-16">
@@ -50,7 +51,11 @@ export function renderGuideView({ dom, t }) {
<div class="guide-section-grid mt-16">
${renderGuidePanel("guide.race_title", ["guide.race_1", "guide.race_2", "guide.race_3", "guide.race_4", "guide.race_4a", "guide.race_5", "guide.race_6", "guide.race_7", "guide.race_8", "guide.race_9", "guide.race_10"], "", { t })}
${renderGuidePanel("guide.manage_steps_title", ["guide.manage_steps_1", "guide.manage_steps_2", "guide.manage_steps_3", "guide.manage_steps_4", "guide.manage_steps_5", "guide.manage_steps_6", "guide.manage_steps_7", "guide.manage_steps_8"], "", { t })}
${renderGuidePanel("guide.manage_steps_title", ["guide.manage_steps_1", "guide.manage_steps_2", "guide.manage_steps_3", "guide.manage_steps_4", "guide.manage_steps_5", "guide.manage_steps_6", "guide.manage_steps_7", "guide.manage_steps_8", "guide.manage_steps_9"], "", { t })}
</div>
<div class="guide-section-grid mt-16">
${renderGuidePanel("guide.schedule_title", ["guide.schedule_1", "guide.schedule_2", "guide.schedule_3", "guide.schedule_4"], "", { t })}
</div>
<div class="guide-section-grid mt-16">
@@ -65,7 +70,7 @@ export function renderGuideView({ dom, t }) {
<div class="guide-section-grid mt-16">
${renderGuidePanel("guide.team_title", ["guide.team_1", "guide.team_2", "guide.team_3", "guide.team_4", "guide.team_5", "guide.team_6"], "", { t })}
${renderGuidePanel("guide.qualifying_title", ["guide.qualifying_1", "guide.qualifying_2", "guide.qualifying_3", "guide.qualifying_4", "guide.qualifying_5"], "", { t })}
${renderGuidePanel("guide.qualifying_title", ["guide.qualifying_1", "guide.qualifying_2", "guide.qualifying_3", "guide.qualifying_4", "guide.qualifying_5", "guide.qualifying_6", "guide.qualifying_7"], "", { t })}
</div>
<div class="guide-section-grid mt-16">
@@ -78,11 +83,11 @@ export function renderGuideView({ dom, t }) {
</div>
<div class="guide-section-grid mt-16">
${renderGuidePanel("guide.audio_title", ["guide.audio_1", "guide.audio_2", "guide.audio_3", "guide.audio_4", "guide.audio_5", "guide.audio_6", "guide.audio_7", "guide.audio_8"], "", { t })}
${renderGuidePanel("guide.audio_title", ["guide.audio_1", "guide.audio_2", "guide.audio_3", "guide.audio_4", "guide.audio_5", "guide.audio_6", "guide.audio_7", "guide.audio_8", "guide.audio_9"], "", { t })}
</div>
<div class="guide-section-grid mt-16">
${renderGuidePanel("guide.decoder_roles_title", ["guide.decoder_roles_1", "guide.decoder_roles_2", "guide.decoder_roles_3", "guide.decoder_roles_4", "guide.decoder_roles_5", "guide.decoder_roles_6", "guide.decoder_roles_7", "guide.decoder_roles_8", "guide.decoder_roles_9", "guide.decoder_roles_10", "guide.decoder_roles_11", "guide.decoder_roles_12", "guide.decoder_roles_13", "guide.decoder_roles_14"], "", { t })}
${renderGuidePanel("guide.decoder_roles_title", ["guide.decoder_roles_1", "guide.decoder_roles_2", "guide.decoder_roles_3", "guide.decoder_roles_4", "guide.decoder_roles_5", "guide.decoder_roles_6", "guide.decoder_roles_7", "guide.decoder_roles_8", "guide.decoder_roles_9", "guide.decoder_roles_10", "guide.decoder_roles_11", "guide.decoder_roles_12", "guide.decoder_roles_13", "guide.decoder_roles_14", "guide.decoder_roles_15"], "", { t })}
${renderGuidePanel("guide.host_title", ["guide.host_1", "guide.host_2", "guide.host_3", "guide.host_4", "guide.host_5", "guide.host_6", "guide.host_7", "guide.host_8"], "", { t })}
</div>