diff --git a/.github/ISSUE_TEMPLATE/guide_stuck.yml b/.github/ISSUE_TEMPLATE/guide_stuck.yml
index 4d704b37..9249797d 100644
--- a/.github/ISSUE_TEMPLATE/guide_stuck.yml
+++ b/.github/ISSUE_TEMPLATE/guide_stuck.yml
@@ -13,8 +13,8 @@ body:
id: where
attributes:
label: Where in the guide
- description: The chapter and step. Filled in for you if you came from the guide.
- placeholder: "01 Flash, step 3 — Hold BOOT, tap RST, let go."
+ description: The guide (Build, Play or Make), the chapter and the step. Filled in for you if you came from the guide.
+ placeholder: "Play · 01 Flash, step 5 — Hold BOOT, tap RST, let go."
validations:
required: true
- type: textarea
diff --git a/.gitignore b/.gitignore
index 8e84e5ef..e6ccd81c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,8 +15,12 @@ node_modules/
dist/
build/
out/
-# Keep the /build app route (source, not a build artifact)
+# Keep the /build app route (source, not a build artifact), and every other
+# folder called build/ in the web source — the guide's /guide/build route.
!web/src/app/build/
+!web/src/**/build/
+# The Build guide's models (case halves, print plates) are served from here.
+!web/public/guide/build/
# Arduino build artifacts
*.hex
diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md
index fedb1b51..de619ea5 100644
--- a/BUILD_GUIDE.md
+++ b/BUILD_GUIDE.md
@@ -78,7 +78,7 @@ Key sourcing rules (details in the BOM README):
>
> **What happened** ([#221](https://github.com/engmung/Patternflow/issues/221)): a board powered through USB-C ran **completely fine for 20–30+ minutes** — no heat, no symptoms — and then suddenly started **smoking at one of the connector pins**, rapidly frying the receptacle and the power path around it. Whether that was a hand-soldering defect on those tight-pitch pins or a structural limit of the 14-pin THT part under the LED matrix's peak current was never settled — so rather than ship an input nobody should populate, **v3.9 removed the footprint entirely.**
>
-> ⚠️ **This is why "it seems to work" proves nothing here.** The failure is *delayed* — passing your multimeter checks and running for half an hour does not mean the joint is safe.
+> ⚠️ **This is why "it seems to work" proves nothing here.** The failure is *delayed* — running fine for half an hour does not mean the joint is safe.
>
> The screw terminal has none of this history — it's the original, proven Patternflow power input, and it's exactly as capable.
@@ -108,8 +108,8 @@ Any fab works, though — upload **`hardware/pcb/gerber/patternflow_v3.9_gerber.
| Your printer bed | Print | Notes |
|---|---|---|
-| **256 mm** (P1S / X1C / A1 class) | `bed_256mm/encloser.stl` | Body frame, back panels, and LED-panel mount in one STL. **White PLA.** ~10 h total. |
-| **~330 mm+** (H2S class) | `bed_330mm/encloser.stl` | The one-piece original — body, closing part, and LED-panel mount in one STL, **no bonding at all**. **White PLA.** Adds a USB pass-through to the DevKit and a cable exit slot — **for wired MIDI/OSC and flashing, not for power** (power is always `J4`, Section 2). |
+| **256 mm** (P1S / X1C / A1 class) | `bed_256mm/encloser.stl` | Body frame (the LED panel's mounting tabs are part of it), back panels and covers in one STL. **White PLA.** ~10 h total. |
+| **~330 mm+** (H2S class) | `bed_330mm/encloser.stl` | The one-piece original — body (panel-mounting tabs built in) and closing part in one STL, **no bonding at all**. **White PLA.** Adds a USB pass-through to the DevKit and a cable exit slot — **for wired MIDI/OSC and flashing, not for power** (power is always `J4`, Section 2). |
| **Everyone** | `knobs/knobs_20mm.stl` | **Required for every build** — all four knobs in one plate, as its own separate print job. **Black PLA.** (15 mm-shaft encoders → `knobs_15mm.stl`.) |
Two colors, on purpose: **body in white, knobs in black** — that contrast is the Patternflow look. Printing the knob plate as a separate job keeps it simple (no color changes mid-print).
@@ -122,7 +122,7 @@ Two colors, on purpose: **body in white, knobs in black** — that contrast is t
-*Left: `encloser.stl` on a P1S bed. Right: everything that comes out of the one print — body frame, back panels, battery cover, and the LED-panel mounting part.*
+*Left: `encloser.stl` on a P1S bed. Right: everything that comes out of the one print — the body frame with its panel-mounting tabs, the back panels and the covers.*
### Bond the printed halves — right after printing
@@ -152,12 +152,6 @@ All parts are through-hole, and the video covers the complete order.
> |---|---|
> | | |
-Before first power, go over your joints with a multimeter:
-
-
-
-Don't plug the ESP32 DevKit in until after the first power check (Section 7).
-
ESP32 pin reference — the full 44-pin map. You don't need it for a normal build; it's here for debugging and derivative designs.
@@ -206,9 +200,9 @@ By now the enclosure halves you bonded in Section 4 have cured and the board is
> Printed the **330 mm one-piece body**? Everything in this section is identical — the only thing you skipped is the bonding step in Section 4.
-1. **Seat the LED panel in the enclosure with its `HUB-75E IN` connector toward the top** — that's the side the ribbon reaches `J1` from. ⚠️ The panel insertion is very tight — near-zero clearance. Work it in slowly.
+1. **Seat the LED panel in the enclosure from the front, with its `HUB-75E IN` connector toward the top** — that's the side the ribbon reaches `J1` from. ⚠️ The panel insertion is very tight — near-zero clearance. Work it in slowly.
-2. **Fit the LED-panel mounting part and tighten the screws.** The mounting part is a separate piece precisely because panel bolt-hole positions vary between suppliers — match it to your panel first.
+2. **Screw the panel to the frame's mounting tabs from behind.** On the standard print the tabs are part of the frame and line up with the BOM-linked panel's holes. Printed the `for_other_panels/` variant (Section 4)? Its mount is a separate, adjustable piece, because bolt-hole positions vary between suppliers — match it to your panel first, then screw.
3. **Set the board into its bay and secure the encoders from the front**: attach each encoder's nut and tighten with a wrench or pliers — this locks the board against the front face.
@@ -233,8 +227,7 @@ By now the enclosure halves you bonded in Section 4 have cured and the board is
*Cable already through the enclosure hole, wires clamped into `J4`, board about to go in.*
-4. **Before inserting the ESP32:** with power disconnected, continuity-check +5V↔GND at the `J3` terminals (open = good, like the Section 5 check). Then power up once and confirm ~5V across `J3`.
-5. Power off, seat the ESP32 DevKit in its sockets (orientation per silkscreen), power on.
+4. With power off, seat the ESP32 DevKit in its sockets (orientation per silkscreen), then power on.
## 8. Firmware
@@ -244,7 +237,7 @@ The ESP32-S3 module is flashed **separately, outside the PCB**. Already seated i
No installation required — desktop **Chrome or Edge** only (Web Serial; Firefox/Safari won't work).
-> 🎬 **See it before you do it.** [patternflow.work/guide](https://patternflow.work/guide) walks through this section — the port, BOOT and RST, the Wi-Fi step, the Basics pack — on a Patternflow you can turn, and every step has a link for telling us where you got stuck.
+> 🎬 **See it before you do it.** [patternflow.work/guide/play](https://patternflow.work/guide/play) walks through this section — the port, BOOT and RST, the Wi-Fi step, the Basics pack — on a Patternflow you can turn, and every step has a link for telling us where you got stuck.
> 🔌 **Use the LEFT USB-C port** — the one on your left when the two ports face you. On the ESP32-S3 DevKit that's the board's **native USB** port (labeled `USB`); the browser flasher (Web Serial + Improv) talks to it directly. The right-hand port goes through a separate USB-to-UART bridge chip and is the one Arduino IDE uses (§8.2) — the flasher may still write the firmware through it, but the Wi-Fi step never appears there, because the firmware only listens for it on the left port.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb34f42c..a5e2471a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,7 +22,7 @@ All notable changes to Patternflow will be documented in this file, newest first
### Web
-- **A guide you can play along with, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). The first page is the first hour, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. The second, [/guide/make](https://patternflow.work/guide/make), is 05 Community and 06 Pattern Lab: the community's real screens, and a board that plays your own Pattern Lab draft (read, never written) while the real Lab opens in a window beside it. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says where (`guide_stuck.yml`).
+- **A guide you can play along with, in three parts, at [/guide](https://patternflow.work/guide)** (work in progress, marked WIP). `/guide` is where you pick one by where your Patternflow is, each with its chapters, in order. **Build**, at [/guide/build](https://patternflow.work/guide/build), is for soldering one from bare parts: 01 Gather (the parts list is `bom_v3.9.csv` itself), 02 Order & print, 03 Solder, 04 Into the case, 05 Wire & power, 06 Firmware (which hands over to Play's 01 Flash) and 07 Check & close, each step played on the real v3.9 board and case in 3D, in the order the build happens; the board's known issues stay in `BUILD_GUIDE.md` §10, linked. **Play**, at [/guide/play](https://patternflow.work/guide/play), is the first hour with a built one, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. **Make**, at [/guide/make](https://patternflow.work/guide/make), is 01 Community and 02 Pattern Lab, done by hand: on a big enough screen your own Pattern Lab, a practice community (placeholder patterns, connected to nothing) and a practice AI with set answers sit beside the steps, and a pointer shows each move and waits for yours; phones get the real screens. Sound, MIDI & OSC, MQTT, Clock, Performance and Editions join Make as they are written. Each guide numbers its own chapters from 01. Links into the first version, when Play was `/guide` itself (`/guide#flash`, `/guide#knobs-3`), land on the same step under `/guide/play`. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says which guide, chapter and step (`guide_stuck.yml`).
- **The community's Performances note says .pfs**, which is what the Director saves, instead of "Save-JSON".
- **A moderator can take a pattern or deck off the wall without deleting it.** Somebody else's public pattern or deck now offers moderators *Make private*, with an optional reason: the softer removal, for things like the same pattern posted a fifth time. Nothing about the work changes and its author keeps it — they can still open it, edit it, take it into the lab — it just stops being shown to anyone else. On a pattern the reason is posted underneath as the moderator's comment, where the author can answer it; either way the author is told in their alerts. While the take-down stands the author cannot make it public again (a take-down its subject can undo in one click is a request); *Restore to public* puts it back exactly as it was. A moderator still cannot publish what an author made private themselves. A deck slot left empty by a take-down reads "made private by a moderator", not "by its author". Moderators can now comment on private patterns and keep their alerts about them, so the thread under a take-down works both ways. Migration `0025` adds `hidden_at` to patterns and decks; `check:hidemod` drives it all through the real routes.
- **One press of Publish is one post.** A double-click on *Publish to the wall*, or a mouse switch that bounces, could put the same pattern on the wall twice in the same second ([#455](https://github.com/engmung/Patternflow/issues/455)). The button's disabled state was React state, a render too late for the second click, and after a successful publish it came back enabled while the page changed. The publish, thread, reply, deck, header, performance and report forms now refuse a second press in the same tick (`useSubmitLatch`), and a published pattern keeps its button down until the page changes; a thread whose files failed to attach offers *Open the thread* instead of posting it again. Behind them the server answers an identical submission from the same account within a minute with the post it already made, looked up and inserted in one synchronous transaction so two requests arriving together cannot both get through. `check:publish` fires two identical publishes into the route at the same instant; a version that awaits between the lookup and the insert fails it.
diff --git a/README.md b/README.md
index c5012907..f4726cb0 100644
--- a/README.md
+++ b/README.md
@@ -143,7 +143,7 @@ What a knob does to a running pattern is the pattern's own decision, so the same
The device functions themselves are fixed. Encoder 1 is brightness. Encoder 4 opens the pattern list, where you turn to browse the names and long-press again to load one, choosing from whatever is installed at the time. Encoder 2 puts the board's IP address on the panel, and typing that into a browser opens the web console, which carries every feature the device has and works from a phone.
-[patternflow.work/guide](https://patternflow.work/guide) has all of this on a Patternflow you can turn, press and hold, from flashing the ESP32 to the console.
+[patternflow.work/guide/play](https://patternflow.work/guide/play) has all of this on a Patternflow you can turn, press and hold, from flashing the ESP32 to the console.
## On the device
diff --git a/firmware/toolchain/console_demo.py b/firmware/toolchain/console_demo.py
index dad45d86..a44db1ec 100644
--- a/firmware/toolchain/console_demo.py
+++ b/firmware/toolchain/console_demo.py
@@ -1231,6 +1231,19 @@ def main():
L.replace(BASE + 'index.html?v=' + load().build);
}
}, true);
+ // The chrome hands the site this panel's address on every link there
+ // (?device=), and the site keeps it as the reader's own panel. This panel
+ // is simulated: its address must never be saved as theirs. Bubbling, so it
+ // runs after the chrome's capture-phase listener has added it.
+ D.addEventListener('click', function (e) {
+ var p = e.composedPath ? e.composedPath() : [], a = null, i, u;
+ for (i = 0; i < p.length; i++) if (p[i].tagName === 'A' && p[i].href) { a = p[i]; break; }
+ if (!a) return;
+ try { u = new URL(a.href); } catch (x) { return; }
+ if (!/(^|\.)patternflow\.work$/.test(u.hostname) || !u.searchParams.has('device')) return;
+ u.searchParams.delete('device');
+ a.href = u.href;
+ });
D.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !e.defaultPrevented) tell({ action: 'escape' });
});
diff --git a/hardware/case/README.md b/hardware/case/README.md
index a7298ce1..cce08070 100644
--- a/hardware/case/README.md
+++ b/hardware/case/README.md
@@ -10,8 +10,8 @@
| Your printer bed | Print | Design | Status |
|---|---|---|---|
-| **256 mm** (Bambu P1S / X1C / A1 class) | [`bed_256mm/encloser.stl`](bed_256mm/) | The snap-fit design split for a 256 mm bed — body frame, back panels, and LED-panel mount in one STL | ✅ Standard option, ~10 h total |
-| **~330 mm+** (Bambu H2S-class) | [`bed_330mm/encloser.stl`](bed_330mm/) | The one-piece original — body, closing part, and LED-panel mount in one STL, no bonding at all. Also has a **USB pass-through to the DevKit and a cable exit slot** (data only — see below) | ✅ Main design ([#113](https://github.com/engmung/Patternflow/issues/113)) |
+| **256 mm** (Bambu P1S / X1C / A1 class) | [`bed_256mm/encloser.stl`](bed_256mm/) | The snap-fit design split for a 256 mm bed — body frame (LED-panel mounting tabs built in), back panels and covers in one STL | ✅ Standard option, ~10 h total |
+| **~330 mm+** (Bambu H2S-class) | [`bed_330mm/encloser.stl`](bed_330mm/) | The one-piece original — body (LED-panel mounting tabs built in) and closing part in one STL, no bonding at all. Also has a **USB pass-through to the DevKit and a cable exit slot** (data only — see below) | ✅ Main design ([#113](https://github.com/engmung/Patternflow/issues/113)) |
**Whichever body you print, also print [`knobs/knobs_20mm.stl`](knobs/) — required for every build.** Knobs go in **black**, everything else in **white**; run the knob plate as its own print job. (15 mm-shaft encoders → `knobs_15mm.stl`; 15 mm and 20 mm shafts are functionally identical, and the [BOM](../bom/)'s reference part, PEC11R-4220F-S0024, is 20 mm.)
@@ -19,7 +19,7 @@
The original mass-production-oriented design: a single-piece body plus a snap-fit closing part, wall-mount hanger hole included. No bonding step at all. Does **not** fit a 256 mm bed.
-**`encloser.stl`** — everything in one STL (body, closing part, LED-panel mount), all **white** PLA; knobs from [`knobs/`](knobs/) in black. Same design as the print-&-assembly-verified 256 mm kit, just uncut.
+**`encloser.stl`** — everything in one STL (body with its LED-panel mounting tabs, closing part), all **white** PLA; knobs from [`knobs/`](knobs/) in black. Same design as the print-&-assembly-verified 256 mm version, just uncut.
**Wired access to the DevKit (330 mm only).** Two openings the 256 mm variant doesn't have yet: a **pass-through between the power-bank bay and the ESP32**, so a USB-C lead reaches the DevKit's own port with the case shut — that's for wired features like MIDI and OSC, and for flashing — and a **cable exit slot below the battery compartment**, so a cable leaves the enclosure instead of being pinched under the back panel.
@@ -27,9 +27,9 @@ The original mass-production-oriented design: a single-piece body plus a snap-fi
## `bed_256mm/` — the standard build
-**`patternflow_v3.3mf`** is the Bambu Studio project for this kit — four pre-arranged plates with the print settings below, the same file the MakerWorld listing serves. Open it and print; the STL is for every other slicer.
+**`patternflow_v3.3mf`** is the Bambu Studio project for this print — four pre-arranged plates with the print settings below, the same file the MakerWorld listing serves. Open it and print; the STL is for every other slicer.
-**`encloser.stl`** puts the whole body in one file: frame and back-panel halves plus the LED-panel mounting part, all in **white** PLA. ~10 hours total on a P1S. Print the knobs separately from [`knobs/`](knobs/), in **black**. The LED-panel mount is sized for the panel linked in the [BOM](../bom/). **Print & assembly verified** — the [assembly video](https://youtu.be/J9C9bZgkNKs) builds from this exact file.
+**`encloser.stl`** puts the whole body in one file: frame halves (the LED panel's mounting tabs are part of them), back-panel halves and covers, all in **white** PLA. ~10 hours total on a P1S. Print the knobs separately from [`knobs/`](knobs/), in **black**. The tabs are placed for the panel linked in the [BOM](../bom/). **Print & assembly verified** — the [assembly video](https://youtu.be/J9C9bZgkNKs) builds from this exact file.
Design perks: **two wall-mount holes**, a **snap-fit back panel**, and recesses for the LED matrix's alignment bumps — **no more nipper-trimming** the panel back (the old [#19](https://github.com/engmung/Patternflow/issues/19) workaround).
@@ -41,7 +41,7 @@ Design perks: **two wall-mount holes**, a **snap-fit back panel**, and recesses
### Assembly notes (both 256 mm variants)
-The full photo sequence lives in the [build guide](../../BUILD_GUIDE.md) — §4 (bond the printed halves right after printing) and §6 (panel in with `HUB-75E IN` toward the top → mounting part + screws → board in, encoder nuts from the front). Two watch-outs: the panel insertion is very tight (near-zero clearance) — work it in slowly — and bonded seams can show a hairline gap, fillable with putty or baking soda + CA.
+The full photo sequence lives in the [build guide](../../BUILD_GUIDE.md) — §4 (bond the printed halves right after printing) and §6 (panel in from the front with `HUB-75E IN` toward the top → screws into the frame's tabs from behind → board in, encoder nuts from the front). Two watch-outs: the panel insertion is very tight (near-zero clearance) — work it in slowly — and bonded seams can show a hairline gap, fillable with putty or baking soda + CA.
## `legacy_v2/` — v2.x boards only
diff --git a/web/public/guide/build/case-split.glb b/web/public/guide/build/case-split.glb
new file mode 100644
index 00000000..36017763
Binary files /dev/null and b/web/public/guide/build/case-split.glb differ
diff --git a/web/public/guide/build/plates.glb b/web/public/guide/build/plates.glb
new file mode 100644
index 00000000..df3883ec
Binary files /dev/null and b/web/public/guide/build/plates.glb differ
diff --git a/web/public/guide/console-demo/index.html b/web/public/guide/console-demo/index.html
index 6709dd43..280f8d21 100644
--- a/web/public/guide/console-demo/index.html
+++ b/web/public/guide/console-demo/index.html
@@ -2,7 +2,7 @@
-
+
Patternflow
diff --git a/web/public/guide/console-demo/knobs.html b/web/public/guide/console-demo/knobs.html
index 28fe630e..3521e587 100644
--- a/web/public/guide/console-demo/knobs.html
+++ b/web/public/guide/console-demo/knobs.html
@@ -1,7 +1,7 @@
-
+
Patternflow - Knobs