Skip to content

[DONE] 정리 4단계 PR 12 — MQTT 벡터가 채널과 스냅숏을, REST 벡터가 /api/mqtt 를 가상 패널에 돌려 본다 - #82

Merged
patternflow-bot merged 5 commits into
devfrom
claude/ops-cleanup-4-pr12-y6rwjb
Oct 15, 2026
Merged

patternflow-bot merged 5 commits into
devfrom
claude/ops-cleanup-4-pr12-y6rwjb

Conversation

@patternflow-bot

@patternflow-bot patternflow-bot commented Oct 15, 2026 •

Copy link
Copy Markdown

ops / https://claude.ai/code/session_01EPRDTPDwckZBFHTbCWekXj

정리 4단계 계획서 표의 PR 12 다. MQTT 계약의 채널과 스냅숏을 벡터 여섯 개로, /api/mqtt 를 REST 벡터 일곱 개로 적었고, 이미 있는 check:mqtt-vectors 와 check:rest-vectors 가 그대로 가상 패널에 돌려 본다. 머지되면 채널 1~4 의 구독자 고정, Broadcast 에 스냅숏이 없는 것, 스냅숏이 쓴 값을 덮는 것, 비밀번호를 돌려주지 않는 것이 스펙이나 가상 패널과 어긋날 때 PR 마다 drift 검사가 빨개진다.

좋아지는 것: 없음

할 일

한 일

MQTT 벡터 여섯, 저마다 mqtt-spec.md 한 문장이다. 채널은 스펙 11행대로 POST /api/mqtt 의 prefix 로 고른다. 펌웨어가 받는 channel 인자는 스펙에 없다.

  • channels-prefix.json, 14행 「Prefix patternflow is Broadcast; …」. ch3 에 붙으면 patternflow/param/1 은 무시하고 patternflow3/param/1 은 따른다. prefix 일곱(patternflow, patternflow1, 2, 4, 5, patternflow6, studio)마다 /api/mqtt 의 channel 이 broadcast, ch1, ch2, ch4, live, custom, custom 이다.
  • channels-forced.json, 14행 「Channels 1–4 force the Subscriber role.」 Live 에선 발행자로 붙는다. 채널 1~4 로 옮기면 구독자고, 옮긴 다음 role=publisher 를 따로 보내도 구독자다. ch4 에서 pattern 을 따른다.
  • channels-snapshot.json, 45행. 채널 15 의 스냅숏을 retain 으로 브로커에 두고 차례로 옮기면, 옮길 때마다 그 채널의 param 넷이 잡힌 값이 된다(110140, 210240, … 510540).
  • channels-heartbeat.json, 45행 둘째 문장. Live 발행자는 붙자마자 retain 스냅숏을 내고, 7.9초 동안 아무것도 보내지 않다가 다음 0.2초 안에 또 낸다. 구독자로 다시 붙으면 브로커에 남은 제 스냅숏을 받아 넷 다 잡힌다.
  • channels-overwrite.json, 45행 셋째 문장. ch2 와 Live 에서 param/N 900 이 먹고, 뒤에 온 스냅숏이 그 값을 덮는다.
  • channels-broadcast.json, 47행. Broadcast 구독자는 retain 스냅숏을 받지 않고, param/2 800 은 스냅숏이 와도 남는다.

REST 벡터 일곱, /api/mqtt 다. rest-vectors.ts 는 MQTT sender 를 넘기지 않아 브로커가 없고, 패널은 붙지 않은 채로 답한다(PR 11 이 넘긴 말).

  • mqtt-fields.json, 367행 「Also reported: …」. 예시의 키와 normal* 다섯, directorHost 를 타입으로 본다.
  • mqtt-password.json, 367행 「Passwords are never returned — only whether one is set.」 처음엔 hasPassword 가 false. 폼으로 host, user, password 를 보내면 답과 다음 GET 에서 hasPassword, normalHasPassword 가 true 이고 password 도 pass 도 없다.
  • mqtt-forces-sub.json, 362행 forcesSub. Live 발행자를 ch3 으로 옮기면 구독자에 forcesSub true, role=publisher 를 보내도 그대로다.
  • mqtt-knobs.json, 363행. 손으로 돌린 클릭이 쌓이고(K1 3+2), 음수가 되고(K2 −4), 역할 off, 발행자, 구독자 어디서나 바로 보인다.
  • mqtt-role.json, 111행, status 의 mqttRole. POST /api/mqtt?role= 세 번의 답과 status 가 같다.
  • mqtt-same-fields.json, 323행. 손과 POST /api/params 다음 /api/mqtt 와 /api/status 의 knobs, params, paramActive 가 같다.
  • mqtt-caps.json, 613행. MQTT 빌드의 caps 는 ["patterns","params","sleep","mqtt"] 이고 /api/mqtt 는 JSON 200 이다. 기본 빌드 쪽은 PR 7 의 absent-* 가 본다.

증거

로컬, web/, dev merge(78381ec)와 mqtt-caps.json 뒤:

$ git diff --stat origin/dev...HEAD
 docs/mqtt-vectors/channels-broadcast.json   | 14 ++++++++++++++
 docs/mqtt-vectors/channels-forced.json      | 20 ++++++++++++++++++++
 docs/mqtt-vectors/channels-heartbeat.json   | 16 ++++++++++++++++
 docs/mqtt-vectors/channels-overwrite.json   | 19 +++++++++++++++++++
 docs/mqtt-vectors/channels-prefix.json      | 20 ++++++++++++++++++++
 docs/mqtt-vectors/channels-snapshot.json    | 22 ++++++++++++++++++++++
 docs/rest-api-vectors/mqtt-caps.json        | 10 ++++++++++
 docs/rest-api-vectors/mqtt-fields.json      | 27 +++++++++++++++++++++++++++
 docs/rest-api-vectors/mqtt-forces-sub.json  | 11 +++++++++++
 docs/rest-api-vectors/mqtt-knobs.json       | 17 +++++++++++++++++
 docs/rest-api-vectors/mqtt-password.json    | 17 +++++++++++++++++
 docs/rest-api-vectors/mqtt-role.json        | 11 +++++++++++
 docs/rest-api-vectors/mqtt-same-fields.json | 18 ++++++++++++++++++
 13 files changed, 217 insertions(+)

$ npm run check:mqtt-vectors
docs/mqtt-vectors/channels-broadcast.json: same
docs/mqtt-vectors/channels-forced.json: same
docs/mqtt-vectors/channels-heartbeat.json: same
docs/mqtt-vectors/channels-overwrite.json: same
docs/mqtt-vectors/channels-prefix.json: same
docs/mqtt-vectors/channels-snapshot.json: same
docs/mqtt-vectors/topics-*.json: same          (PR 11 의 아홉, 그대로)
passed   mqtt-vectors
# 한 번에 2 s 안. 열 번 연달아 exit 0. 4코어에 여덟 개씩 나란히 세 번, 24번 다 exit 0.

$ npm run check:rest-vectors
docs/rest-api-vectors/mqtt-caps.json: same
docs/rest-api-vectors/mqtt-fields.json: same
docs/rest-api-vectors/mqtt-forces-sub.json: same
docs/rest-api-vectors/mqtt-knobs.json: same
docs/rest-api-vectors/mqtt-password.json: same
docs/rest-api-vectors/mqtt-role.json: same
docs/rest-api-vectors/mqtt-same-fields.json: same
docs/rest-api-vectors/{absent,device,patterns,state}-*.json: same   (PR 6, PR 7 의 서른넷, 그대로)
passed   rest-vectors

# channels-heartbeat.json 의 out, 패널 쪽
step 02 out mqtt {"topic":"patternflow5/snapshot","payload":"<string>","retain":true} by 20 ms
step 03 out mqtt <absent> by 7900 ms
step 04 out mqtt {"topic":"patternflow5/snapshot","payload":"<string>","retain":true} by 200 ms
step 06 status.paramActive[0..3] true

# 깨뜨린 일곱. 각각 git checkout 으로 되돌렸고, 되돌린 뒤 passed
# 1) mqtt.ts: Broadcast 도 스냅숏을 구독한다 (showPrefix 를 /^patternflow[1-5]?$/ 로)
docs/mqtt-vectors/channels-broadcast.json: drift
  only in the vector: step 03 status.paramActive[0] false, … step 07 status.params[1] 800
  only in the panel: step 03 status.paramActive[0] true, … step 07 status.params[1] 200
failed   mqtt-vectors
# 2) mqtt.ts: HEARTBEAT_MS 8000 을 10000 으로
docs/mqtt-vectors/channels-heartbeat.json: drift
  only in the vector: step 04 out mqtt {"topic":"patternflow5/snapshot","payload":"<string>","retain":true} by 200 ms
  only in the panel: step 04 out mqtt nothing by 200 ms
failed   mqtt-vectors
# 3) mqtt.ts: ch4 가 구독자로 묶지 않는다 (forcesSub 를 /^ch[1-3]$/ 로)
#    첫 판 channels-forced.json 은 이걸 통과시켰다. 고친 판:
docs/mqtt-vectors/channels-forced.json: drift
  only in the vector: step 10 status.mqttRole "subscriber", step 11 status.mqttRole "subscriber", step 13 status.active "Wave Saw"
  only in the panel: step 10 status.mqttRole "publisher", step 11 status.mqttRole "publisher", step 13 status.active "Origin"
failed   mqtt-vectors
# 4) mqtt.ts: ch1 이 묶지 않는다 (/^ch[2-4]$/)
docs/mqtt-vectors/channels-forced.json: drift
  only in the vector: step 03 status.mqttRole "subscriber", step 04 status.mqttRole "subscriber"
  only in the panel: step 03 status.mqttRole "publisher", step 04 status.mqttRole "publisher"
failed   mqtt-vectors
# 5) mqtt.ts: ch3 이 묶지 않는다 (/^ch[124]$/), REST 스위트
docs/rest-api-vectors/mqtt-forces-sub.json: drift
  only in the vector: step 02 reply.body.forcesSub true, step 02 reply.body.role "subscriber", … step 04 reply.body.role "subscriber"
  only in the panel: step 02 reply.body.forcesSub false, step 02 reply.body.role "publisher", … step 04 reply.body.role "publisher"
failed   rest-vectors
# 6) mqtt-http.ts: GET /api/mqtt 의 답에 password 를 싣는다
docs/rest-api-vectors/mqtt-password.json: drift
  only in the vector: step 02 reply.body.password <absent>, step 03 reply.body.password <absent>
  only in the panel: step 02 reply.body.password "hunter2", step 03 reply.body.password "hunter2"
failed   rest-vectors
# 7) mqtt-spec.md 45행 "every 8 seconds" 를 "every 10 seconds" 로
docs/mqtt-vectors/channels-heartbeat.json: drift
  only in the vector: says docs/mqtt-spec.md
  only in the panel: says not in docs/mqtt-spec.md
failed   mqtt-vectors

$ npm run typecheck                    # exit 0
$ npm run lint -- --max-warnings=0     # exit 0
$ npm run check:drift
passed bom, bom-schematic, changelog, editions, features, links-absolute, markers, mqtt-vectors, osc-vectors, presets, rest-vectors, shared, virtual-panel
skipped roadmap                        # 원래 레포를 못 읽는다. CI 는 PF_DRIFT_STRICT=1 로 돈다
$ npx tsx scripts/drift.ts markers     # passed
$ python3 ../.github/scripts/check_links.py
all 725 relative links resolve
$ npm run check:human-lock
check:human-lock: no locked words changed
$ npm run check:voice                  # JSON 은 읽지 않는다. 이 본문은 - 로 읽혔다
check:voice: nothing found
$ git fetch --tags && npm run doc-map  # CSV 는 git checkout 으로 되돌렸다
1111 links from 222 files → docs/investigations/2026-10-doc-map.csv
0 do not resolve
1366 Korean/English pairs → docs/investigations/2026-10-doc-parity.csv
7 en-short, 18 ko-short, 1 missing-ko, 1340 ok

doc-map 은 이 PR 이 바꾸지 않는다. 벡터는 JSON 이라 링크를 더하지 않는다.

가상 패널의 POST 순서를 본 실행. 버릴 스크립트로 start({ features: ["mqtt"], clock: "manual" }) 한 패널에 차례로 보냈다(커밋하지 않음):

prefix=patternflow3&role=subscriber  {"role":"subscriber","channel":"ch3","prefix":"patternflow3"}
prefix=patternflow5&role=publisher   {"role":"subscriber","channel":"live","prefix":"patternflow5"}
role=publisher                       {"role":"publisher","channel":"live","prefix":"patternflow5"}

GitHub, 이 PR: head 8c72eb5 에서 「Drift checks / check」(job 116503126802, PF_DRIFT_STRICT=1) 로그에 passed mqtt-vectors, passed rest-vectors, passed roadmap, check:human-lock: no locked words changed. 「Docs point at files that exist / check」(run 38758476175)도 success. web/** 가 바뀌지 않아 「Web CI」는 돌지 않았고, typecheck 와 lint 는 위의 로컬 결과다. [DONE] 직전 dev 는 070afe5 그대로라 더 merge 할 것이 없다.

실행법

cd web && npm ci
npm run check:mqtt-vectors
npm run check:rest-vectors

가정

  • 기준 브랜치는 dev(머리말). 표가 PR 12 에 적은 두 자리(docs/mqtt-vectors/channels-*.json, docs/rest-api-vectors/mqtt-*.json)에만 더했다. README 둘은 k6, k11 행이고 이미 channels- 와 mqtt- 를 말해서 고치지 않았다. CHANGELOG.md, docs/rfc-cleanup-progress.md, doc-map CSV, rest-vectors.ts, tools/virtual-panel/ 도 안 고쳤다. 라벨은 앞 PR 들처럼 달지 않았다.
  • 채널은 prefix 로 고른다. 스펙 11행이 POST /api/mqtt 가 받는 것으로 host, port, user, password, prefix, role 을 꼽고, 14행이 「The prefix is the channel」이라 한다. 펌웨어의 channel 인자는 스펙에 없어 쓰지 않았다(D8). 펌웨어는 이 POST 에서 prefix 를 받지 않는다. 아래 for an issue.
  • prefix 와 role 을 한 요청에 보내면 가상 패널은 role 을 지금 채널에 먼저 적용하고 그다음 prefix 로 옮긴다(위 실행). 스펙은 둘의 순서를 말하지 않는다. 그래서 역할이 걸리는 곳(channels-forced.json, mqtt-forces-sub.json)은 prefix 와 role 을 따로 보낸다. 깨뜨린 3 을 첫 판이 통과시킨 까닭이 이것이었다.
  • 하트비트 창은 7.9초 조용함과 그다음 0.2초다. 스펙은 「every 8 seconds」에 허용 폭을 말하지 않는다. 가상 패널의 프레임은 16.7 ms 라 스냅숏은 붙은 지 8.013초에 나간다.
  • 스냅숏의 페이로드는 "<string>" 로만 본다. 스펙은 「compact JSON of the mid-join state, including param:[a,b,c,d]」 라 param 말고 어떤 키가 있는지 정하지 않는다. 벡터가 보내는 스냅숏도 param 만 싣는다. 구독자가 pattern 이나 message 로 무엇을 하는지는 스펙에 없다(D8).
  • channels-heartbeat.json 의 다시 붙는 쪽은 패널이 발행자일 때 남긴 제 스냅숏을 구독자로 받는다. 값이 기본 500 이라 params 는 그대로고, paramActive 가 넷 다 true 가 되는 것으로 스냅숏이 왔음을 본다. 그 사이 브로커에 스냅숏을 보낸 것은 패널뿐이다.
  • message 의 retain 칸(broadcast: yes · channels: no)은 벡터가 없다. 보내는 쪽이 정할 플래그이고, 패널은 message 를 받기만 한다. 패널 쪽에서 돌려 볼 것이 없다. /api/mqtt 의 channel 이 off 가 되는 때도 스펙이 말하지 않아 보지 않았다.
  • mqtt-same-fields.json 이 인용한 323행의 뒷부분(MQTT 없는 빌드는 그 경로가 없다)은 PR 7 의 absent-routes.json 이 본다. features 가 벡터마다 하나라 한 벡터에 둘을 담을 수 없다.
  • mqtt-caps.json 은 caps 의 순서까지 본다. 110행이 기본 셋을 먼저 적고 「each edition adds its own」이라 하고, 펌웨어도 코어 셋 뒤에 feature 의 cap 을 붙인다(core_status_http.h 251~264행).
  • REST 벡터는 rest-vectors.ts 에 MQTT sender 가 없어 브로커 없이 돈다. host 를 주는 것은 mqtt-password.json 하나이고 역할이 off 라 아무 데도 걸지 않는다.
  • 스크린샷은 없다. 화면이 없는 PR 이다.
  • firmware/ 와 web/src/ 를 건드리지 않아 build.sh all, check:ci, check:panels 는 §2.1(1) 대상이 아니다. 계획서대로 typecheck 와 이 PR 의 검사, 깨뜨린 실행을 적었다.

Changelog

Docs

  • The MQTT vectors reach the channels and the snapshot, and /api/mqtt has REST vectors. Six more in docs/mqtt-vectors/ replay the prefix naming the channel, channels 1–4 forcing Subscriber, the retained snapshot on channels 1–4 and Live, the Publisher's eight-second heartbeat, a snapshot overwriting a param/N write, and Broadcast taking no snapshot. Seven in docs/rest-api-vectors/ read GET and POST /api/mqtt: its fields, the password it never returns, forcesSub, the knob counts in every role, mqttRole in status and the mqtt cap.

for an issue

  • POST /api/mqtt and the broker. docs/mqtt-spec.md line 11: "Set on the panel's MQTT console page or with POST /api/mqtt (host, port, user, password, prefix, role)." The firmware's postOnLoop() (features/mqtt/core_mqtt_http.h lines 148 to 214) reads only channel and role, answers a request with neither 400 {"ok":false,"error":"channel or role required"} (line 162), and ignores host, port, user, password and prefix. The broker is saved by POST /api/mqtt/config (configOnLoop(), line 216), which takes the password as pass (line 241). Vectors that set the prefix or the password there: every channels-*.json, mqtt-password.json (step 02), mqtt-forces-sub.json (steps 01 and 02). web/scripts/drift/_broker.ts (PR 11) points every MQTT vector at its broker the same way.
  • Nothing else. Each vector was set against core_mqtt.h: snapshot only on patternflow1 to patternflow5 (isChannelShowPrefix(), line 296, used at 949 and 964), the heartbeat (SNAPSHOT_HEARTBEAT_MS, line 107, from handle(), line 1431), a Publisher ignoring a non-empty snapshot (line 728), channels 1 to 4 forcing Subscriber (lines 1100, 1131, 1305, 1333), knobs from the hand in every role until knob/N arrives (lastKnobsCopy(), line 1374, and update(), line 1456), and no password in sendStatus() (core_mqtt_http.h lines 73 to 133).

for another k

  • k10's file, for the maintainer: tools/virtual-panel/transports/mqtt-http.ts applies role on the channel the panel is on and saves prefix after it. On ch3, POST /api/mqtt?prefix=patternflow5&role=publisher leaves a Subscriber on Live, and a second role=publisher is needed (run above). The spec does not order the two, and a client reading it would expect a Publisher on Live. The vectors here send the two apart, so changing the order breaks none of them.
  • k11's README, for the maintainer: its paragraph on what has no vectors could add the message retain column, for the reason in 가정. The plan gives that file to no later k.
  • k23: nothing from this PR.

Upstream

  • What: Add six MQTT vectors to docs/mqtt-vectors/ (channels-: the prefix as the channel, channels 1–4 forcing Subscriber, the retained snapshot, the Publisher's heartbeat, a snapshot overwriting a write, Broadcast with no snapshot) and seven REST vectors for GET and POST /api/mqtt to docs/rest-api-vectors/ (mqtt-).
  • Checks: npm run typecheck, npm run lint, npm run check:mqtt-vectors and npm run check:rest-vectors pass; firmware/ and web/src/ are untouched.
  • Docs map: none. The vectors are JSON and add no links.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EPRDTPDwckZBFHTbCWekXj

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPRDTPDwckZBFHTbCWekXj
Six channels-* vectors replay the channels and the snapshot in mqtt-spec.md; six mqtt-* REST vectors read GET and POST /api/mqtt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPRDTPDwckZBFHTbCWekXj
Asked in the same request as the prefix, the role lands on the channel the panel is leaving, so ch4 passed even when it did not force Subscriber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPRDTPDwckZBFHTbCWekXj
PR 7's absent-* vectors read the default build only; this is the other side of the same convention line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPRDTPDwckZBFHTbCWekXj
@patternflow-bot patternflow-bot changed the title [WIP] 정리 4단계 PR 12 — MQTT 벡터가 채널과 스냅샷, /api/mqtt 를 가상 패널에 돌려 본다 [DONE] 정리 4단계 PR 12 — MQTT 벡터가 채널과 스냅숏을, REST 벡터가 /api/mqtt 를 가상 패널에 돌려 본다 Oct 15, 2026
@patternflow-bot
patternflow-bot marked this pull request as ready for review October 15, 2026 15:18
@patternflow-bot
patternflow-bot merged commit 1384154 into dev Oct 15, 2026
2 checks passed
@patternflow-bot
patternflow-bot deleted the claude/ops-cleanup-4-pr12-y6rwjb branch October 15, 2026 15:20
engmung pushed a commit to engmung/Patternflow that referenced this pull request Oct 15, 2026
engmung pushed a commit to engmung/Patternflow that referenced this pull request Oct 15, 2026
Patternflow-lab#11
Patternflow-lab#12
Patternflow-lab#13
Patternflow-lab#15
Patternflow-lab#17
Patternflow-lab#14
Patternflow-lab#16
Patternflow-lab#19
Patternflow-lab#20
Patternflow-lab#18
Patternflow-lab#21
Patternflow-lab#23
Patternflow-lab#24
Patternflow-lab#25
Patternflow-lab#26
Patternflow-lab#28
Patternflow-lab#29
Patternflow-lab#30
Patternflow-lab#27
Patternflow-lab#31
Patternflow-lab#32
Patternflow-lab#34
Patternflow-lab#35
Patternflow-lab#33
Patternflow-lab#36
Patternflow-lab#37
Patternflow-lab#38
Patternflow-lab#39
Patternflow-lab#40
Patternflow-lab#41
Patternflow-lab#42
Patternflow-lab#43
Patternflow-lab#46
Patternflow-lab#44
Patternflow-lab#47
Patternflow-lab#48
Patternflow-lab#49
Patternflow-lab#45
Patternflow-lab#50
Patternflow-lab#51
Patternflow-lab#52
Patternflow-lab#54
Patternflow-lab#57
Patternflow-lab#55
Patternflow-lab#53
Patternflow-lab#56
Patternflow-lab#59
Patternflow-lab#58
Patternflow-lab#60
Patternflow-lab#61
Patternflow-lab#62
Patternflow-lab#63
Patternflow-lab#64
Patternflow-lab#65
Patternflow-lab#66
Patternflow-lab#67
Patternflow-lab#68
Patternflow-lab#69
Patternflow-lab#70
Patternflow-lab#72
Patternflow-lab#73
Patternflow-lab#71
Patternflow-lab#75
Patternflow-lab#74
Patternflow-lab#77
Patternflow-lab#78
Patternflow-lab#76
Patternflow-lab#79
Patternflow-lab#80
Patternflow-lab#81
Patternflow-lab#82
Patternflow-lab#84
Patternflow-lab#83
Patternflow-lab#85
Patternflow-lab#86
Patternflow-lab#87
Patternflow-lab#88
Patternflow-lab#90
Patternflow-lab#91
Patternflow-lab#89
Patternflow-lab#92
patternflow-bot added a commit that referenced this pull request Oct 16, 2026
… 않는다 (#94)

* Add horizontal desktop printed enclosure and assembly instructions. Switch username to mbchars.

* tools: case-models builds mbchars' horizontal desktop case

mbchars.py puts the remix's printed parts together from its STLs (no
assembled file exists): the three front sections where the joint bars'
36 mm insert pitch puts their lug holes, the covers on the sixteen inserts,
the USB retainer on its boss and the stands on the bottom edge, each step
checked against the README (410.28 x 176.16 x 42 mm), the board's encoder
grid, the 320 x 160 mm panel and boolean overlaps. assemble.mjs learns
ledTurn (the panel lies the long way here) and knobLook (the official knob,
printed white), and lib/looks.mjs pla_orange, the 3MF's own #F46B16. The
other three models rebuild byte-identical; the new one is 380 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBYm64PnfneJq2ZDJ8Gs7

* web: /build's case switch and 3D preview get a fourth case, mbchars' horizontal desktop one

The card is the remix README's own facts and photos (resized to 1200 px),
the preview shows the device in the case, and the switch is four tabs, two
by two on a phone. A case too wide for the view at the one scale (this one,
41 cm across on a desktop's tall canvas) is drawn smaller by as little as
keeps it clear of the arrows; every case still fits at the one scale on a
phone, and the three upright ones on a desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBYm64PnfneJq2ZDJ8Gs7

* docs: changelog for mbchars' horizontal desktop case and the fourth preview case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBYm64PnfneJq2ZDJ8Gs7

* web: ZuperZuche's build joins the map - the first print, from Moscow

Found by accident, with the PCBs ordered ten minutes later; the 3D printer came
a month on, bought for the case, and the case was the first thing it printed.
Moscow was given, so the pin is on the city.

The blurb stops at the printer. The maker's note about making the patterns with
AI is left out on purpose, and there are no pronouns for the maker: the message
does not say.

Three photos: a pixel-art Mount Fuji beside a modular synth, a pixel-art frog
pond seen from above, and a cat plush in front of the panel.

groups.test.ts pins the number of countries on the real map; Russia is new, so
11 becomes 12.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* web: the 3D preview names whose case it is, in the build map's label

On the Build tab the name on the case's tab (Official, Besoiobiy,
SimonePDA, mbchars) stands over the device, on the build map's dark
highlight. It follows the pick at once, as the tab does, and reads
Official for a case whose model did not load. The knob hint moves under
it while it is up. Changelog also gets the line for the arrows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* web, docs: the first flash starts in download mode, /flash goes to the guide's Flash chapter, and the README is new from its first screen

The Play guide's 01 Flash (and /ko) puts hold BOOT, tap RST before Flash
Patternflow on every first flash, assumes the Wi-Fi step after
"Installation complete!" usually does not come (RST, Flash Patternflow
again, Connect to Wi-Fi), and ends on the panel's own patternflow-xxxx
network at 192.168.4.1 for when that does not take; the device simulator
draws the NETWORK screen's hotspot. The Build guide's flash card, the
Pattern tab's notes and the breadboard page give the same order. The bare
/flash, which the device's Update page and every release's FLASHING.md
point at, redirects to /guide/play#flash; /flash/manifest.json and
/flash/bin/* are untouched. flash.test.ts pins the card order and anchors.

The README opens with a one-line definition, the hero, the
photosensitivity warning, four doors (Build, Play, Make patterns, Add
sound) and First light; the story, community, facts and editions follow,
shorter. The docs that give the first flash (firmware/README.md,
PATTERN_GUIDE.md, SUPPORT.md, docs/assembly/) say the same order, the
indexes list the guide pages, the three case remixes and the bundles that
exist, and stale wording (the custom slots, "variant", the dead /flash
page) is gone. CONTRIBUTING.md gets a row for the interactive guide's
copy, and the pull request template asks for the row and the link check.

From #7 (caaf2c1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VaFtEmtx2AkdH1wnbQpV3Z

* docs: the README opens with the fixed lines and three ways in, and First light follows the build

The opening is L1 + L2 + L3 from docs/manifesto.md (Paik moves to Where it
began, in L5's own words), with Play one in your browser, Build one and Get
one assembled under the badges. First light moves below the build, gains the
steps it was missing (unplug before seating, Origin as the sign of success,
Format storage on the first Basics install) and states the J4 rule as data
never power on the DevKit's ports. Stale lines are corrected against the
repo: sound and MQTT shipped in editions, MIDI arrived in 3.9.0, 3.10.5
fixed four requests, and the photosensitivity warning is the manifesto's
full text.

From #8 (bfc4b22).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VaFtEmtx2AkdH1wnbQpV3Z

* docs: BUILD_GUIDE flashes in the Play guide's order, and the README's campaign is still open

BUILD_GUIDE §8.1 now goes download mode first (hold BOOT, tap RST),
Flash Patternflow, the Wi-Fi as a second go after RST, the DevKit seated
with the power bank unplugged, then the panel's own patternflow-xxxx
network (K2 shows its name before the hotspot is up), then the Basics
pack: the Play guide's 01 Flash card for card. §7 no longer seats the
DevKit before it has firmware, so it is Wiring, and 7.4 names the step
that powers up. The README's First light points at §8 again, and Crowd
Supply reads as it stands: past its goal, open until 15 October; the
200% figure stays in the dated timeline. Besoiobiy's case README cites
the renumbered steps.

From #9 (16eca1b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VaFtEmtx2AkdH1wnbQpV3Z

* ## What changed

**New journal post** — `fine` (ko + en), published 2026-10-13. Not highlighted.

Putting the blog off but not past a week, a hand-written paper diary after months, being selected for Anthropic's Claude startup program, reading and music slipping, AI making time feel more pressing, the interview shoot for the introduction video at the startup center studio, the funding ending in two days, the apartment renovation that still hasn't happened, the visually impaired artist enjoying the Patternflow at the Seoul Station teardown, and parts bought for a workshop.

**Two photos**, resized to 2000 px on the long side:
- the interview studio (faces already pixelated in the source), after the interview paragraph; also the cover, since the author prefers not to lead with a face
- the Seoul Station exhibition before the teardown, after the teardown paragraph

## Notes

- The draft pasted the Korean section twice in `_temp/blog.md`; the second copy was clean and is the one used, so the text matches the English paragraph for paragraph (13 each).
- Verified against the dev server: both language versions return 200, both images serve, and the index hero is this post with the interview photo as its thumbnail. Not in the highlighted rows.
- `dev` and `main` were identical before this commit, so the PR carries only the post.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
BODY
journal: new post "fine"

Post (ko+en), not highlighted: the paper diary, the Claude startup
program, the interview shoot for the introduction video, the
funding ending in two days, the apartment still unrenovated, and the
visually impaired artist enjoying the Patternflow at the teardown.

Two photos at 2000 px: the interview studio (faces already
pixelated), also the cover, and the exhibition before the teardown.
The draft pasted the Korean section twice; the clean second copy is
used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: the cleanup has a baseline

From #11

* docs: cleanup step 1 is planned

From #12

* docs: the v2 build guide moved to `docs/legacy/BUILD_GUIDE_v2.md`

From #13

* docs: the doc map lists the Korean/English pairs

From #15

* docs: the doc map reads the YAML under `.github/`

From #17

* docs: `CHANGELOG.md` is split by release into `docs/releases/`

From #14

* docs: the community files moved to `.github/` and the audio guide to

From #16

* docs: cleanup step 2 is planned

From #19

* docs: cleanup step 3 is planned

From #20

* docs: the project's writing voice is written down

From #18

* docs: cleanup step 4 is planned

From #21

* docs: the docs index and the repository map catch up with step 1 of the

From #23

* docs: cleanup step 5 is planned

From #24

* web: `check:features` fails when /features and the feature folders

From #25

* web: `check:links-absolute` fails on a GitHub link into the repository

From #26

* hardware: `check:bom` and `check:bom-schematic` hold the BOM to the

From #28

* web: `check:editions` fails when the shelf and the bundles disagree

From #29

* web: `check:presets` fails when a web preset cannot reach a panel

From #30

* web: `check:human-lock` fails a pull request that changes a word of the

From #27

* web: `check:changelog` fails when `CHANGELOG.md` and `docs/releases/`

From #31

* web: every pull request runs the drift checks and the text lock

From #32

* web: `check:roadmap` fails when a /roadmap node and its issues disagree

From #34

* docs: the contributor docs say what CI runs on every pull request

From #35

* web: `check:voice` points at the habits of generated prose, line by line

From #33

* docs: `AGENTS.md` carries the writing-voice rule and the step 2 check

From #36

* docs: the doc map is regenerated at the close of step 2 of the cleanup

From #37

* web: five of the site's twelve lint warnings are gone

From #38

* web: the seven `<img>` lint warnings carry a named exception

From #39

* docs: where the panel size lives, line by line

From #40

* web: pattern Lab's July storage keys say who wrote them and when

From #41

* web: the site's redirects and old routes say what links them

From #42

* web: web CI fails on a lint warning

From #43

* firmware: the MIDI page counts 24 detents a turn, as the encoder has

From #46

* web: the community's old formats say what wrote them and when

From #44

* firmware: the vendored HUB75 driver names the upstream commit it came

From #47

* firmware: the vendored HUB75 driver takes upstream's fix for its config

From #48

* web: a new TODO or FIXME turns the drift checks red

From #49

* web: one file holds the values the firmware and the site both carry

From #45

* docs: the any-resolution RFC has a draft

From #50

* docs: the Linux player has a draft RFC

From #51

* firmware: the firmware reads the values it shares with the site from

From #52

* web: the site's lint and test tooling is on its latest releases inside

From #54

* docs: `AGENTS.md` names `shared/`, the HUB75 driver's `UPSTREAM.md` and

From #57

* docs: the two step 5 drafts are listed in `docs/README.md`

From #55

* web: the site reads the panel size and the knob numbers from

From #53

* web: drizzle is on its latest patch releases

From #56

* docs: the repository maps catch up with step 3

From #59

* web: tailwind is on its latest 4.x release

From #58

* web: react Three is on its latest releases inside the current majors

From #60

* web: zustand is on its latest release inside the current major

From #61

* web: mediabunny is on its latest release inside the current major

From #62

* web: changes from #63

From #63

* web: posthog-js is on its latest release inside the current major

From #64

* web: better-auth is on its latest release inside the current major

From #65

* web: react is on its latest release inside the current major

From #66

* web: next is on its latest release inside the current major

From #67

* docs: the doc map is regenerated at the close of step 3 of the cleanup

From #68

* web: the guide's simulated board takes input that no hand made

From #69

* tools: a virtual panel for the contract vectors

From #70

* tools: the virtual panel answers device state over REST

From #72

* tools: the virtual panel answers the pattern routes

From #73

* tools: contract vectors have a format and a replay

From #71

* web: `check:virtual-panel` runs every preset on the virtual panel

From #75

* tools: the virtual panel speaks MQTT

From #74

* tools: the virtual panel answers `/api/mqtt`

From #77

* tools: the virtual panel speaks OSC

From #78

* docs: the REST contract has its first vectors

From #76

* docs: the MQTT contract has its first vectors

From #79

* tools: oSC has contract vectors

From #80

* docs: the REST vectors reach the rest of the core

From #81

* docs: the MQTT vectors reach the channels and the snapshot, and

From #82

* tools: the virtual panel takes the audio WebSocket

From #84

* tools: the virtual panel speaks RTP-MIDI

From #83

* tools: the audio WebSocket has contract vectors

From #85

* tools: the virtual panel sends MIDI back

From #86

* tools: mIDI has contract vectors

From #87

* tools: mIDI out and `/api/midi` have contract vectors

From #88

* docs: the maps of the tree name the virtual panel and the contract

From #90

* docs: the REST and MIDI specs say one thing where they said two

From #91

* docs: `AGENTS.md` names the virtual panel, the contract vectors and

From #89

* docs: the doc map is regenerated at the close of step 4 of the cleanup

From #92

* docs: changelog for 81 workshop fork PRs

#11
#12
#13
#15
#17
#14
#16
#19
#20
#18
#21
#23
#24
#25
#26
#28
#29
#30
#27
#31
#32
#34
#35
#33
#36
#37
#38
#39
#40
#41
#42
#43
#46
#44
#47
#48
#49
#45
#50
#51
#52
#54
#57
#55
#53
#56
#59
#58
#60
#61
#62
#63
#64
#65
#66
#67
#68
#69
#70
#72
#73
#71
#75
#74
#77
#78
#76
#79
#80
#81
#82
#84
#83
#85
#86
#87
#88
#90
#91
#89
#92

* 시작 — 정리 마지막 기록을 원래 레포 dev 위에 다시 짠다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpjXsYZbwT5PpwzpNCEEqq

* docs: the doc map lists the six links the cleanup's closing entry adds

The progress log's entries for steps 1 to 5 and the gate link
contract-vectors.md, the doc-parity list, the panel-geometry record and
the two RFC drafts. The cleanup RFC's status line links the log. Each of
the six resolves. The log and the RFC come over with the cleanup's
records, so until they land the map is six rows ahead of the tree.

From #93 (2f094fe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpjXsYZbwT5PpwzpNCEEqq

---------

Co-authored-by: Mykyta Bilous <belousnikita@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Lee Seunghun <122682380+engmung@users.noreply.github.com>
Co-authored-by: pi-hub <pi-hub@localhost>
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