Skip to content

20261014 - Add a guided tutorial across Summary, Home and Configuration - #120

Merged
Purple10101 merged 5 commits into
mainfrom
20261014-guided-tutorial
Oct 21, 2026
Merged

Purple10101 merged 5 commits into
mainfrom
20261014-guided-tutorial

Conversation

@jfremerman

@jfremerman jfremerman commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Re-opened from #118 under the author's own GitHub account, as the reviewer asked; same code (commit eb1e309 on #113, rebased onto current main). Last of the 4 PRs split out of #113. This branch also carries the Configuration page commit from #119, because the tour points at sections that PR adds; once that merges, this PR reduces to the one tutorial commit. #116 and #117 are already on main.

What

/tutorial starts a guided tour drawn over the real Summary, Home and Configuration pages. Each step dims the page, lights one region, points an arrow at it and says a sentence or two in a card, with Previous, Next and Skip Tutorial. The wizard's last page recommends it, and Configuration gains a Tutorial section under Other that launches it at any time.

Why

A new owner finishes the setup wizard and lands on pages nobody has explained: which card opens what, what the dots on the radar display mean, which settings are worth touching, and how to tell a good signal from an overloaded one.

How it works

  • The server owns the steps (src/tutorial.py) and every step is a URL, so a reload or the Back button keeps the place and a stale or hand-edited link shows the plain page.
  • The owner moves between pages by clicking the lit card or tab, so the tour teaches the navigation it depends on.
  • Steps about the radar views and the signal meter carry an example picture.
  • The Summary steps only run on a network with more than one node; every run ends on a closing Summary step. 29 steps with Summary, 24 without.
  • While the wizard is in progress /tutorial redirects to it, because Home and Configuration both bounce there.
  • The card and arrows stay hidden until a smooth scroll has settled; laid out mid-scroll they flashed in the wrong place.
  • The overload step describes the reading and help box from 20261007 - Show when the radio is overloaded, now that it keeps running #110.

For the reviewer

  • ADS-B picture: static/tutorial/adsb-map.jpg is a real capture, used as is. Jonathan and Josh agreed to keep it uncropped.
  • signal-overload.png is rendered from the page with simulated readings and should be replaced with a real capture when there is one.
  • The Minimum SNR step states the default as 7; that default is set in retina-node and was not checked from this repository.
  • Known limits: the margins only fit the card beside the Configuration page in a window of about 1580 px or wider, narrower than that the card sits over part of the page; at phone width several steps have no arrow.

Testing

Docs

New docs/features/tutorial.md, added to the map in docs/README.md; docs/architecture.md, docs/features/config-editor.md, docs/features/fleet-and-naming.md and docs/features/setup-wizard.md updated.

After this

A real overload capture, and the setup wizard update to match the regrouped Configuration page.

Review fixes (20261020)

Three commits: the Minimum SNR copy, the three blocking items, and the Tracker's
Below SNR layer.

Minimum SNR copy

The Configuration tour's fifth tuning step, "Tracking: Minimum SNR", lit the Tracking section and
its side-nav link, both of which are gone in #119, so the step had nothing to point at. The tuning
run is four steps again and keeps its order: what to watch, what too far looks like, and what to
change.

The Home tour's Tracker step also offered "tracks below the SNR threshold" as a way to sort the
Tracker. retina-tracker v0.5.0 tracks every detection and has no per-site SNR gate, so that layer
is always empty and the step was recommending a filter that can never show anything. The step now
names only the sorts that still do something. The layer itself is handled in #119.

The Tracker's Below SNR layer

Carried over from the Configuration review fixes: the rest of that work merged in #119, and this
is the part of it that had not landed, so it rides here rather than on a branch of its own.

The Tracker's Below SNR layer is removed. The same v0.5.0 change made the below_snr class
permanently empty, while the page still described it as detections "dropped at the SNR gate before
the tracker considered it at all" — a filter worth trying, that can only ever show nothing. An
owner who picked it saw an empty scope and had no way to tell that from a quiet sky.

Nothing is lost. The class is still read from the stream, still counted against the untracked cap
and still drawn under All, so a node on an older tracker shows those detections with the rest
instead of behind their own button. A layer remembered in localStorage falls back to All unless
the page still offers it, so an owner who had Below SNR selected does not open on an empty layer,
and a hand-edited value cannot select one either.

Start tutorial could send the owner back into the wizard

The last step offered Start tutorial and Go to dashboard from the moment it rendered, while
the two requests that end the wizard were still in flight with their failures swallowed.
save-step {complete} is the one that clears the step file and writes the completion flag, and
until it lands is_setup_wizard_in_progress is still true, so home.index, the config blueprint
and tutorial.start all redirect back to /set-up. An owner who clicked too quickly, or at all
if the save had failed, went straight back into the wizard they had just finished.

showStep now skips the generic save for the last step, and enterHooks.complete calls
finishSetup, which awaits that one save and reveals the two links only once it has returned. A
failure shows the server's reason and a Try again button rather than nothing, and costs
nothing else: the step file still holds the previous step, so a reload resumes there.
/set-up/complete is still posted without being awaited, after the gate — it brings the radar
stack up, takes tens of seconds, never raises, and nothing on the step depends on it.

A run could be renumbered while it was open

A step is addressed by its number in the run, and the run is 28 steps long when Summary is toured
and 23 when it is not, so the number means nothing without the list. _include_summary asked
discovered_nodes on every request, so a peer appearing or dropping mid-tour swapped the list
underneath the owner: the same ?tutorial=6 stopped being Home's first step and became
Configuration's welcome.

The question is asked once now, by start, and the answer travels in the URL as summary=1 the
way demo=1 already does, with demo implying it. A link carrying a number but no shape is read
as a single node's run, and payload already renders the plain page when the number does not name
a step there.

Skip did not stick

Each page of a tour was a pushed history entry carrying ?tutorial=, and close cleans only the
entry it is standing on, so one press of Back brought the tour straight back. Moves within a run
now use location.replace, so a whole run occupies the single entry it started in: there is one
entry to clean, and Back leaves the tour rather than walking it backwards. Nothing records the
dismissal and nothing needs to, because Back now lands on the page from before the tour, which
carries no parameter.

Testing

test_tutorial.py gains a peer appearing and a peer dropping mid-run, both asserting the payload
is unchanged; that every step URL carries the run's shape; that a demo link works carrying only
demo=1; that the script holds no location.assign; and that the wizard's last step starts with
both links hidden, offers a retry, and does not fire-and-forget the completion save. Four existing
tests now pass summary=1 where they relied on the node count being re-read. Full suite passed on
the rebased stack, ruff clean.

Not run in a browser or on a node. All three fixes are browser behaviour at heart — a button
reveal, a history entry, a URL parameter — so a manual pass on a node is the real verification.

Docs

tutorial.md: "Which steps run" records that the shape is decided once and carried in the URL, and
why a step number is meaningless without it; "How a run moves" says navigation replaces rather than
pushes; a new "Skip" section explains the single history entry and what used to go wrong.
setup-wizard.md: "Completion" is rewritten around the gate — why nothing on the last step is
clickable before the save lands, what a failure does, and why /set-up/complete is still not
awaited.


Generated by Claude Code

@Purple10101 Purple10101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used the tour by hand on a test node and it's great: the copy is clear, the pictures help, and moving between pages by clicking the lit card teaches the navigation nicely. What needs work is how it interacts with the rest of the node's state, so this review is mostly about how those interactions were found and proven, because that's the part worth carrying into future changes here.

How to find the interactions

In retina-gui, a page is rarely just a page. Most bugs come from state the feature reads but doesn't own, changing between two requests. For this PR I listed every piece of state the tour depends on, then asked three questions of each one.

1. What does the feature read, and who else writes it?

The tour reads Who else writes it
Whether the wizard is in progress (setup-wizard.json, via is_setup_wizard_in_progress()) setup.js on every step change (save-step); /set-up/save-step {complete}; the 24 h expiry
How many nodes there are (discovered_nodes()) The mDNS browser and the /healthz prober, continuously, on their own threads
Whether a calibration is running Auto-Calibrate and Quick Calibrate; the before_request hold redirects GETs while it runs
The radar mode (mode.txt) /api/mode, /set-up/complete, and the reset on every retina-gui restart
The step number in the URL The browser: reload, Back and Forward, bookmarks, autocomplete

2. Can any of those change between two requests of one tour? A tour is many requests over minutes. Anything recomputed on every request (_include_summary()) or redirected by a hook (/ and /config while the wizard runs, the calibration hold) can disagree with the request before it.

3. What is in flight at the moment the user can click? Look at the code around your entry point, not just your own code. doTransition() in static/setup.js shows the last page's buttons before save-step {complete} and /set-up/complete have returned. Both are fire-and-forget, and their failures are swallowed. Also remember the browser's own rules: navigating away cancels in-flight fetches, location.assign adds a history entry, and replaceState only rewrites the current one.

How each one was verified

Reading the code gives the suspicion. Hardware gives the proof. Every finding below was reproduced on a test node with an RSPduo, running the current dev release, driven in a real Chromium browser over a remote link. Each time I checked the node's own state, not just the page: the files under /data/retina-gui, the retina-gui journal, and where /, /config and /tutorial redirected afterwards. Where timing mattered, I forced the ordering rather than hoping to hit it:

  • Slow or lost requests: held or aborted one request inside the browser (Playwright page.route) while the page carried on.
  • Exact interleavings: stepped through each request from a shell, so the order was certain.
  • A node leaving or joining: withdrew and restored a second node's mDNS advert, which flips the node count within a few seconds.
  • Wizard and calibration states: seeded setup-wizard.json at a chosen step, and started then cancelled a Quick Calibrate.

The node's settings files were restored byte for byte afterwards.

Blocking

1. Start tutorial can send the owner back into the wizard, sometimes from step 1.
Found by: question 3. The button is live before the node has recorded complete, and /tutorial redirects to /set-up while the wizard is in progress.
Verified: starting on Calibrate, Skip to the last page, click Start tutorial, in four ways:

  • Normal link, click within 60 ms: no race. The save landed first.
  • save-step {complete} held 2.5 s: the click redirected to /set-up, and the navigation cancelled the held save, so it never reached the server (the journal confirms this). The wizard reopened on Calibrate and saved that step again. /, /config and /tutorial all redirected to the wizard.
  • The save lost, click 3 s later: the same outcome, after the page had said "You're all set".
  • Redirect decided just before the save, wizard page rendered just after: the wizard found no resume point, restarted at Agreements, and wrote a fresh setup-wizard.json. Every page then sends the owner back into setup, for up to 24 h or until they finish it again.

The underlying reason it bites so hard: merely loading /set-up saves its first step. A stale /set-up?tutorial=1 link was enough to put the node back "in setup". "Go to dashboard" has always had this exposure, but this PR makes Start tutorial the recommended way out, so far more owners will take it.
Fix: don't reveal the last page's buttons until save-step {complete} has succeeded, and show a retry if it fails. Better, have /set-up/complete record completion itself, in one request the page waits for.

2. The step list can change mid-tour.
Found by: question 2. _include_summary() re-reads the node count on every request, and step numbers are positions in a 28- or 23-step list.
Verified:

  • Peer leaves: with the tour on "Open your node" (step 5 of 28), I hid the second node and clicked the lit card. The tour landed on "Step 6 of 23: Max-Hold", silently skipping five steps.
  • Peer appears: in a single-node run on "Open Configuration" (step 10 of 23), I brought it back and clicked Config. /config?tutorial=11 loaded with no tour.

A neighbour rebooting for an update is enough to trigger this. A peer whose GUI is down also drops out and comes back.
Fix: decide once in /tutorial and carry the decision in every step URL, as demo=1 already is.

3. Skip doesn't stick.
Found by: question 1, the URL row. Every page of a tour is a history entry with ?tutorial=N, and Skip only cleans the current one.
Verified: Skip on Configuration, then browser Back, and the tour reappeared at step 15. A bookmark or autocomplete taken mid-tour does the same.
Fix: use location.replace for page changes within a tour, or have Skip remember that the tour was dismissed.

Smaller

  • The Tracker step still says you can sort by "tracks below the SNR threshold" (src/tutorial.py). retina-tracker v0.5.0 no longer drops anything at an SNR gate, so that layer is always empty.
  • Keyboard users are stuck on the click-through steps. On "Open your node", ArrowRight, Enter and Space do nothing. Tab only reaches Previous and Skip, so the only way on is to quit. Letting Enter or Space on a focused lit target advance would fix it.
  • Pictures. Clicking quickly through Max-Hold, ADS-B Map and Tracker, the picture didn't update until I clicked on it. Each picture took about 0.43 s to arrive over the remote link the first time, and a single <img> is reused with only its src swapped, often while the card is faded out. Preloading the tour's pictures at the start, and giving each step a fresh <img> (or awaiting decode()), should cure it.
  • Step 4 would be better interactive. The overlay swallows every click, so the simulator it describes can't be tried. A per-step flag that lets pointer events through the lit region would do it.
  • Skip in a demo run leaves ?demo=1 in the URL.
  • During a calibration, the hold redirects /?tutorial=N and /tutorial to /config without the tour. Rare, since the overlay blocks starting one, but worth a line in the docs.

Checked and fine

  • The whole tour: all 28 steps at 1920, 1280 and 390 px, each on the right page with its target ringed, and Finish closing on Summary.
  • Navigation: Previous from 28 back to 1 across pages, the arrow keys, the Tab trap, reload keeping your place, and Escape.
  • Bad links: stale and hand-edited links (out of range, wrong page, junk values) show the plain page.
  • The other runs: the 23-step single-node run, and a 28-step demo run that keeps demo=1.
  • Clicks during the tour:
    • A click on the lit node tab stays on this host, although the tab links to the node's .local name.
    • Lit service cards don't open mid-tour.
    • The wiki links open in a new tab with rel="noopener".
  • Modes and settings: Spectrum mode gives the "nothing to point at" note instead of breaking, and reduced motion never hides the card.
  • Cost: tour pages render within about 15 ms of the same pages without a tour.
  • A real end of the wizard at human speed: Towers, a real /towers/select Apply, Skip Calibrate, a pause, then Start tutorial. The tour ran all 28 steps while /set-up/complete finished restarting the radar. By the Configuration steps the live signal meter was reading normally, and the wizard ended up completed.

A checklist for next time

Before a change here is ready, it's worth asking, and then proving on a node:

  • State: does it read wizard state, the node count, calibration, mode, or the restart lock? What happens if that changes between two of my requests?
  • In-flight work: what is still in flight when the user can first click? What if that request is slow, lost, or cancelled by a navigation?
  • The browser: does it survive reload, Back and Forward, a stale or bookmarked link, and a second tab?
  • Conditions: does it work on a single node, in Spectrum and SDRconnect, during a calibration, over a slow remote link, and with only a keyboard?

jfremerman and others added 4 commits October 20, 2026 14:24
A new owner finishes the setup wizard and lands on pages nobody has
explained: which card opens what, what the dots on the radar display mean,
which Configuration settings are worth touching and how to tell a good
signal from an overloaded one.

/tutorial starts a tour drawn over the real pages. Each step dims the page,
lights one region, points an arrow at it and says a sentence or two in a
card placed in the margin, with Previous, Next and Skip Tutorial. The server
owns the steps (src/tutorial.py) and every step is a URL, so a reload or the
Back button keeps the place, and a stale or hand-edited link shows the plain
page. Moving between pages is done by the owner clicking the lit card or
tab, so the tour teaches the navigation it depends on. Steps about the radar
views and the signal meter carry an example picture, so the step can point
at what to look for. The Summary steps only run on a network with more than
one node; every run ends on a closing Summary step that points back at
Resources and Help. There are 29 steps with the Summary steps and 24
without.

The wizard's last page recommends the tour, and Configuration gains a
Tutorial section under Other that launches it at any time. While the wizard
is in progress /tutorial redirects to it, because Home and Configuration
both bounce there and a tour would lose its place.

The card and arrows stay hidden until a smooth scroll has settled. Laid out
mid-scroll they flashed up in the wrong place before jumping to the right
one.

The example pictures in static/tutorial are real screenshots, apart from
signal-overload.png, which is rendered from the page with simulated readings
and should be replaced with a real capture when there is one. The Minimum
SNR step states the default as 7; that default is set in retina-node and was
not verified from this repository. The margins only fit the card beside the
Configuration page in a window of about 1580 px or wider; narrower than that
the card sits over part of the page, and at phone width several steps have
no arrow.

tests/test_tutorial.py is new: step order and numbering, one or two
sentences per step, every target present on its page, image sizes matching
the files, the single-node run, the wizard redirect and stale links. The
overlay's drawing is not covered by the tests: it was checked in a browser
at 1920, 1280 and 390 px wide, and by eye on a test node with a radio
attached. Docs: new
features/tutorial.md, added to the map in docs/README.md; architecture.md,
config-editor.md, fleet-and-naming.md and setup-wizard.md updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AoHZhvWU7Ge89Cm6YgrExB
The Configuration tour ended its tuning run on a fifth step, Tracking: Minimum
SNR, which lit the Tracking section and its side-nav link. Both are gone now
that retina-tracker no longer reads min_snr, so the step had nothing left to
point at: _target("cfg-tracking") would find no element and the side-nav hook
would not exist.

The tuning run is four steps again, and it still has the order that made it
worth splitting: what to watch, what too far looks like, and what to change,
either by calibration or by hand. Nothing moves between pages, so no other
step's targets change.

The Home tour's Tracker step also offered "tracks below the SNR threshold" as
one of the ways to sort the Tracker. retina-tracker v0.5.0 tracks every
detection and has no per-site SNR gate, so the Below SNR layer that refers to
is always empty, and the step was recommending a filter that can never show
anything. The step now names only the sorts that still do something. The layer
itself stays on the Tracker page, because the class is still on the wire and a
node running an older tracker still fills it; the Tracker page's own wording is
corrected in a separate change.

tutorial.md loses the step from its table, stops counting it among the
two-sentence steps, and records why the fifth step went, pointing at
config-editor.md#no-tracking-section rather than repeating the reason. The
Tracker step's row in the same table describes what the step lights up, not its
wording, so it needed no change.

Tests: test_tutorial.py derives its assertions from the step list, so it needed
no edit and now covers four Configuration tuning steps instead of five. It
checks every step's body for one or two sentences, which the shortened Tracker
body still satisfies; no test asserts a step's wording. The full suite was last
run on the step removal alone (1099 passed, with only the known
test_an_unwritable_location_reports_rather_than_raises root-environment
failure); the wording change since then is text in the step list and was not
re-run here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bhhab26X2mbjmbtq7wKdUf
Review of #120 found three. None of them is in the drawing, which is most of
the code; all three are in the state around a run.

The last step of the setup wizard offered Start tutorial and Go to dashboard
from the moment it rendered, while the two requests that end the wizard were
still in flight with their failures swallowed. save-step with complete is the
one that clears the step file and writes the completion flag, and until it
lands is_setup_wizard_in_progress is still true, so home.index, the config
blueprint and tutorial.start all redirect back to /set-up. An owner who clicked
Start tutorial too quickly, or at all if the save had failed, was sent back
into the wizard they had just finished.

showStep now skips the generic save for the last step, and enterHooks.complete
calls finishSetup, which awaits that one save and reveals the two links only
once it has returned. A failure shows the server's reason and a Try again
button rather than nothing, and costs nothing else: the step file still holds
the previous step, so a reload resumes there. /set-up/complete is still posted
without being awaited, because it brings the radar stack up, takes tens of
seconds, never raises, and gates nothing on the page.

Second, a run could be renumbered while it was open. A step is addressed by its
number in the run, and the run is 28 steps long when Summary is toured and 23
when it is not, so the number means nothing without the list. _include_summary
asked discovered_nodes on every request, which meant a peer appearing or
dropping mid-tour swapped the list underneath the owner: the same ?tutorial=6
stopped being Home's first step and became Configuration's welcome. The
question is now asked once, by start, and the answer travels in the URL as
summary=1 the way demo=1 already does, with demo implying it. A link carrying a
number but no shape is read as a single node's run, and payload already renders
the plain page when the number does not name a step there.

Third, Skip did not stick. Each page of a tour was a pushed history entry
carrying ?tutorial=, and close cleans only the entry it is standing on, so one
press of Back brought the tour straight back. Moves within a run now use
location.replace, so a whole run occupies the single entry it started in: there
is one entry to clean and Back leaves the tour rather than walking it
backwards. Nothing records the dismissal and nothing needs to, because Back now
lands on the page from before the tour, which carries no parameter.

Tests: test_tutorial.py gains a peer appearing and a peer dropping mid-run, both
asserting the payload is unchanged; that every step URL carries the shape; that
a demo link works carrying only demo=1; that the script holds no
location.assign; and that the wizard's last step starts with both links hidden,
offers a retry, and does not fire-and-forget the completion save. Four existing
tests now pass summary=1 where they relied on the node count being re-read.
On the rebased stack, which is how this lands, the suite is 1105 passed with
ruff clean and one failure,
test_an_unwritable_location_reports_rather_than_raises, which fails only
because the suite ran as root, where an unwritable directory is still writable.
On this branch before the rebase it is 1101 passed and that failure plus
test_no_token_is_left_without_a_step, which finds cfg-tracking orphaned because
#119 is what removes the section the dropped step pointed at.

Not run in a browser or on a node. The three fixes are all browser behaviour at
heart, so the Tutorial and Setup wizard tabs of the manual test plan are the
real verification.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bhhab26X2mbjmbtq7wKdUf
retina-tracker v0.5.0 tracks every detection and has no per-site SNR gate
(ed9bbea, "Track every detection: no per-site SNR threshold"), which left the
below_snr class permanently empty while the Tracker page still offered it as a
layer worth trying and described it as detections dropped at the SNR gate
before the tracker considered them. An owner who picked it saw an empty scope
with no way to tell that from a quiet sky.

Only the button goes. The class is still read from the stream, still counted
against the untracked cap and still drawn under All, so a node running an
older tracker shows those detections with the rest rather than behind a button
of their own. A layer remembered in localStorage falls back to All unless the
page still offers it, so neither an owner who had Below SNR selected nor a
hand-edited value can select a layer that no longer exists.

This was split out of the Configuration review fixes: the rest of that work
merged in #119, and this is the part of it that had not landed.

Tests: test_tracker_routes.py follows the removed button.

Docs: tracker.md's detection-class table moves below_snr to "All only", stops
saying the tracker drops anything at an SNR gate, and records why the button
went and what still happens to the class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wh14qma4dunvECxn7Wj4YW
@jfremerman
jfremerman force-pushed the 20261014-guided-tutorial branch from f2fcf76 to d262b7b Compare October 20, 2026 18:32
…ast wizard step

#120's fix for the completion race moved /set-up/complete from the moment
the last step rendered to the moment its two links appear. Both links
navigate away, and a navigation cancels an ordinary fetch that has not
left the browser yet. On a test node in Spectrum, with that request held
for 2.5 seconds and Start tutorial clicked straight away, the request
never reached the server: the wizard finished, but the node stayed in
Spectrum with no radar running.

/set-up/complete is now sent with keepalive, as a third argument to
postJSON, and before the links are revealed rather than after. The
wizard already sends its spectrum release with navigator.sendBeacon for
the same reason. Re-run on the same node, the held request reached the
server after the click and the node came back to radar; the slow,
failed and immediate completion cases behave as before.

tests/test_tutorial.py asserts the keepalive and the order. Docs:
setup-wizard.md's Completion section says why.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Purple10101

Copy link
Copy Markdown
Collaborator

Thanks for the fixes. I've re-tested all three on a node and they hold: the wizard can no longer send an owner back into setup, a peer joining or leaving mid-tour keeps the step list, and Skip sticks.

They did open one new gap, which I've fixed directly in 2ae5ac0 on this branch. /set-up/complete now went out at the same moment the two links appeared, so an immediate click could cancel it in the browser. On a node in Spectrum, with that request held 2.5 s, it never reached the server and the node finished setup with no radar. It's now sent with keepalive and before the links show. Re-run on the same node, the radar came back. Details are in the commit and in setup-wizard.md#completion.

Please pull before you push again, so it isn't lost in a force-push.

@Purple10101
Purple10101 merged commit 060f96c into main Oct 21, 2026
3 checks passed
@Purple10101
Purple10101 deleted the 20261014-guided-tutorial branch October 21, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants