diff --git a/.github/ISSUE_TEMPLATE/guide_stuck.yml b/.github/ISSUE_TEMPLATE/guide_stuck.yml
new file mode 100644
index 00000000..4d704b37
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/guide_stuck.yml
@@ -0,0 +1,53 @@
+name: Stuck in the guide
+description: A step of the guide at patternflow.work/guide didn't get you through.
+title: "[guide] "
+labels: ["area:docs", "type:bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks — a report like this shows exactly where the guide needs to be better. If you came from a "Stuck here?" link, the step is already filled in below.
+
+ Need an answer right now rather than a better guide? The [hardware-help channel on Discord](https://discord.com/channels/1497757947827327067/1499907910707187833) is quicker. Writing in Korean is fine too — 한국어로 써도 돼요.
+ - type: input
+ 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."
+ validations:
+ required: true
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: What you did, and what you saw instead of what the guide said you'd see.
+ placeholder: "After flashing, the Wi-Fi box never appeared. Pressing RST didn't bring it up either."
+ validations:
+ required: true
+ - type: textarea
+ id: tried
+ attributes:
+ label: What did you try?
+ description: Anything you tried to get past it, and whether it helped.
+ - type: dropdown
+ id: board
+ attributes:
+ label: Board
+ description: The version is printed on the board's silkscreen.
+ options:
+ - v3.9
+ - v3.0
+ - v2.x
+ - not sure
+ - type: input
+ id: browser
+ attributes:
+ label: Browser and computer
+ description: Filled in for you if you came from the guide. Change it if you did the step on another device.
+ placeholder: "Chrome 140 on Windows"
+ - type: textarea
+ id: photos
+ attributes:
+ label: Photos or a screen recording
+ description: A photo of the panel, or of the screen you were stuck on, often says more than a paragraph. Drag them in here.
diff --git a/AUDIO_GUIDE.md b/AUDIO_GUIDE.md
index f6bcf373..f0448d77 100644
--- a/AUDIO_GUIDE.md
+++ b/AUDIO_GUIDE.md
@@ -48,7 +48,7 @@ the active tab) → enter your panel's address (`patternflow.local` or its IP)
→ the knobs move with the music. (Make sure **Audio-React (AUD)** is turned on
at the top of the console's **Audio** page or via the panel's NETWORK screen.)
-**Then open the editor** (*Editor ↗* in the popup) — this is where it gets
+**Then open the editor** (*Mapping editor ↗* in the popup) — this is where it gets
good. Each of the four knobs is a **box drawn on the live spectrum**: the
box's width is the frequencies it listens to, its height the loudness window
it maps. Drag a box over the bass and knob 1 becomes a bass knob. Inside
@@ -67,7 +67,7 @@ Boxes map to knobs 1:1 and that's fixed on purpose — box 2 *is* knob 2.
A small PDM microphone soldered to the DevKit lets the panel react to the
room itself — no browser, no phone, nothing else running. This is an
optional add-on: the firmware ships with the mic **off** and costs nothing
-until you build and enable it.
+until you solder one on and switch it on.
### What to buy
@@ -125,7 +125,7 @@ Stick the mic wherever sound reaches it. Done.
### Turn it on
-Console → **Audio** page (`/audio-in`) → flip **Microphone** or **Audio-React (AUD)** on. That's the
+Console → **Audio** page (`/audio-in`) → flip **Microphone** on. (**Audio-React (AUD)** beside it is the other input, the extension and the phone app.) That's the
whole switch: on means listening and driving the knobs, off releases the
hardware completely. The **gain** slider (1–16, default 8) is there if your
room runs quiet — PDM mics on this chip are famously low-amplitude, and gain
@@ -175,8 +175,8 @@ computer's address so it reconnects itself after a reboot — is
This is the missing half of [`docs/director-midi.md`](docs/director-midi.md):
the Director's `.mid` export writes CC 20–23, so drop the clip on a MIDI
track, set the track's output to the panel's port, and the show plays on the
-panel from Live's transport. The `MIDI` row on the panel's NETWORK screen
-switches it off without reflashing; `/api/status` reports the session,
+panel from Live's transport. The switch on the console's **MIDI** page turns it
+off without reflashing; `/api/status` reports the session,
sensitivity and message counts under `midi`.
## OSC — Ableton today, anything tomorrow
diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md
index 39d6f5c9..fedb1b51 100644
--- a/BUILD_GUIDE.md
+++ b/BUILD_GUIDE.md
@@ -244,13 +244,16 @@ 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).
-> 🔌 **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 browser flasher won't see the board on that one.
+> 🎬 **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.
+
+> 🔌 **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.
1. Visit **[patternflow.work](https://patternflow.work)** on a desktop browser.
2. Connect the ESP32-S3 to your computer with a USB-C **data cable**, using the **left port** (see above).
-3. Scroll to the **Patterns** section, click **"Flash Patternflow"**, pick the serial port, and follow the on-screen steps. Wi-Fi can be provisioned right there too (Improv-Serial).
-4. Disconnect, seat the module back into the board sockets (orientation per silkscreen), and connect power.
-5. **Load the patterns.** The image ships with **Origin only** — the rest live on the device's filesystem instead of inside the firmware, which is what freed the memory for everything else. Open **[the decks shelf](https://community.patternflow.work/community/decks)** and press **Install to my board** on the **Basics** pack: 33 patterns, one click, no account. Your browser fetches the pack and hands it to the board over your Wi-Fi, so the board is never talking to the internet itself.
+3. Open the **Pattern** tab, find **Got the hardware?**, click **"Flash Patternflow"**, pick the serial port, and follow the on-screen steps. A new install erases the module first; a module already running Patternflow is updated in place and keeps its patterns and Wi-Fi.
+4. **Wi-Fi.** When the install finishes, press **Next** and the flasher asks for your network (Improv-Serial). Type the name exactly — there is no list to pick from, and it is case-sensitive — and use a **2.4 GHz** network; the ESP32-S3 cannot see 5 GHz. If the install ends without asking, press **RST** on the module once, click **Flash Patternflow** again, pick the port, and choose **Connect to Wi-Fi**. Some modules do not restart into Patternflow on their own after flashing; the button does it.
+5. Disconnect, seat the module back into the board sockets (orientation per silkscreen), and connect power.
+6. **Load the patterns.** The image ships with **Origin only** — the rest live on the device's filesystem instead of inside the firmware, which is what freed the memory for everything else. Open **[the decks shelf](https://community.patternflow.work/community/decks)** and press **Install to my board** on the **Basics** pack: 33 patterns, one click, no account. Your browser fetches the pack and hands it to the board over your Wi-Fi, so the board is never talking to the internet itself. On a freshly flashed board the device's page first says **Storage needs formatting**: press **Format storage**, confirm, and the pack installs by itself as soon as the format is done. On Android, `patternflow.local` does not resolve — type the board's IP address (hold **K2**) into the **Device address** field on the Basics card instead.
> 🎛️ **One pattern after flashing is correct, not a failed install.** It used to be 34 baked into the image. They moved out so that patterns can be added and removed without reflashing, which is also how anything you make yourself reaches the panel.
@@ -262,11 +265,13 @@ No installation required — desktop **Chrome or Edge** only (Web Serial; Firefo
>
> The picker should now offer a line like `USB JTAG/serial debug unit (COM4) – Paired`. The number depends on which USB port you used.
>
+> In download mode the firmware cannot answer the flasher, so it treats the module as new and **erases it** — installed patterns and saved Wi-Fi included. Press **RST** alone to bring a module back to normal first if you only meant to update it.
+>
> Still nothing? It is almost always the cable — a charge-only USB-C one enumerates nothing at all. **There is no driver to install on this port**: the ESP32-S3 handles USB itself, so the CP2102 / CH34x links on the flasher's troubleshooting screen do not apply here.
-> 📶 **Changing Wi-Fi later.** The network you set during flashing is **saved on the device and reused on every boot** — it stays until you overwrite it. To move Patternflow to a different Wi-Fi, either **re-flash from the browser** (you'll set the new network during Improv provisioning), or in Arduino IDE do a **full erase** (Tools → *Erase All Flash Before Sketch Upload* → *Enabled*) and re-upload. A plain re-upload does **not** clear the stored credentials.
+> 📶 **Changing Wi-Fi later.** The network you set during flashing is **saved on the device and reused on every boot**. The panel remembers up to five networks — home, studio, a venue — and tries the most recent first, so moving it does not mean reflashing. Add or forget networks on the device's **Wi-Fi** page (the console at `patternflow.local`, or the board's IP address). A plain Arduino IDE re-upload does **not** clear the stored networks; **Tools → *Erase All Flash Before Sketch Upload*** does.
-> 📡 **No Wi-Fi where you are? The panel is one.** About fifteen seconds after it finds no known network, the panel raises its own hotspot: `patternflow-xxxx` (the name on its NETWORK screen - hold K2), password `patternflow`. Join it from a phone or a laptop and open `http://192.168.4.1/` - the whole console, including the Wi-Fi page, so you can add the network for wherever you are next and the panel joins it at once. The phone will say the network has no internet; that is true, and it stays connected. Mode (`auto`, `always`, `off`) and the password are on the console's Wi-Fi page.
+> 📡 **No Wi-Fi where you are? The panel is one.** *(Firmware after v3.10.4; the Performance edition v0.4.0 has it already.)* About fifteen seconds after it finds no known network, the panel raises its own hotspot: `patternflow-xxxx` (the name on its NETWORK screen - hold K2), password `patternflow`. Join it from a phone or a laptop and open `http://192.168.4.1/` - the whole console, including the Wi-Fi page, so you can add the network for wherever you are next and the panel joins it at once. The phone will say the network has no internet; that is true, and it stays connected. Mode (`auto`, `always`, `off`) and the password are on the console's Wi-Fi page.
*Photos from the v2 guide — the flashing flow is identical on v3.*
@@ -290,7 +295,7 @@ OSC, network MIDI and audio-react ship in the **Audio** edition: open [patternfl
2. The panel lights up with the default pattern (Origin) within a second or two.
3. Turn all four knobs — each should visibly change the pattern.
4. Press-click each encoder once; long-press **K4** (~1s) to enter pattern select, rotate to browse, long-press again to exit.
-5. Long-press **K1** for the global brightness mode; **K2** long-press shows the OSC info screen.
+5. Long-press **K1** for the global brightness mode; **K2** long-press shows the NETWORK screen (Wi-Fi state and IP address); **K3** long-press puts each knob's number on the panel.
6. Power-cycle once and confirm it boots cleanly with no RESET press needed (see the GPIO0 note in Section 5 if it doesn't).
7. All good? **Close the back panel**: hook the right edge in first, then press along the snap-fit until it clicks shut (shown at **09:11** in the [assembly video](https://youtu.be/J9C9bZgkNKs)). Press the knobs onto the shafts last.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f500374..eb34f42c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ All notable changes to Patternflow will be documented in this file, newest first
### Firmware
+- **No more stray letters on the panel's own screens.** A portrait line holds ten characters, and Adafruit GFX wraps an eleventh onto the next line by itself: the KNOB MAP's "TURN = SHOW" put a lone "W" in front of "K3 = EXIT", the install screen's "web console" did the same, and the default host name "patternflow" lost its "w" on the hotspot and UPDATE screens. The labels are shorter now ("TURN=SHOW", "console"), and names that can be longer than a line are broken into lines of ten by `drawCenteredFit` — "pattern" / "flow-a1b2", "pattern" / "flow.local" — keeping every line where it was.
+- **A deck sent from the community's dock keeps its order.** The console's one-click install from a link (`/patterns?src=`) took only `.pfm` and `.json` from a build's file list, so the `catalog.txt` every deck build carries was left behind and the deck landed in alphabetical order. It now takes exactly what a dropped zip does (`catalog.txt` and `.pfs` too) - one rule, `installable()`, for every way in.
- **A firmware upload survives a Wi-Fi hitch.** The `/update` connection now waits up to two minutes for a stalled upload instead of five seconds, and the page gives up after five minutes with a message instead of waiting forever. Checked on a panel with a 12 s stall halfway through a flash. `PUT /update` takes a raw image, for `curl -T`. From Simone Majocchi ([#450](https://github.com/engmung/Patternflow/pull/450)).
- **Two 64×64 modules chained make a 128×64 panel.** `PANEL_GEOMETRY` in `config.h` picks the stock 128×64, one 64×64, or two 64×64 daisy-chained (the `firmware64x2` PlatformIO env). The driver is told the module and the chain, and everything else sees the canvas; the stock build is unchanged. From Simone Majocchi ([#446](https://github.com/engmung/Patternflow/pull/446)).
- **Performance v0.4.0 carries the clock and a Weather face**, and its show catalogue holds 100 sequences instead of 48. An MQTT subscriber following a fast sequence stream now drains the broker and applies the latest value per knob each frame instead of falling one message per frame behind (a jump of more than 16 clicks in one frame is capped), and a publisher no longer replays its own retained snapshot when it reconnects. From Simone Majocchi ([#445](https://github.com/engmung/Patternflow/pull/445), [#448](https://github.com/engmung/Patternflow/pull/448), [#449](https://github.com/engmung/Patternflow/pull/449)).
@@ -20,6 +22,8 @@ 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`).
+- **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 512b0114..c5012907 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ You don't need hardware to start. The **[Live Editor](https://patternflow.work/p