From 52c1291d75f135237dd7bed0663eb51f377a8842 Mon Sep 17 00:00:00 2001 From: engmung <122682380+engmung@users.noreply.github.com> Date: Thu, 1 Oct 2026 08:40:26 +0900 Subject: [PATCH 1/7] =?UTF-8?q?web:=20/guide/make=20is=20a=20hands-on=20tu?= =?UTF-8?q?torial=20=E2=80=94=20the=20real=20Lab,=20a=20practice=20communi?= =?UTF-8?q?ty,=20a=20practice=20AI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3D board meant nothing on the second guide, so the page is a desk of small windows now: the reader's real Pattern Lab in a browser window, a practice community (the community's real styles and words, with Origin and Patterns 1-3 as placeholders, nothing fetched), and a practice AI that answers the Lab's two prompts with a real preset's JS and then that preset's real .h. A drawn pointer shows each step and waits for the reader to do it; it never clicks in the Lab. Narrow screens keep the screenshot slides. Played through as a reader in 1440x900 and 1280x800, en and ko, reduced motion; keyboard-only for the community. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- web/src/components/guide/Extras.tsx | 23 +- web/src/components/guide/Guide.module.css | 115 ++++ web/src/components/guide/GuideExperience.tsx | 34 +- web/src/components/guide/LabShots.tsx | 4 +- web/src/components/guide/LabWindow.module.css | 221 +------ web/src/components/guide/LabWindow.tsx | 496 ++------------ web/src/components/guide/copy/community.ts | 57 +- web/src/components/guide/copy/lab.ts | 104 +-- web/src/components/guide/copy/make.ts | 4 +- web/src/components/guide/desk/Desk.module.css | 603 +++++++++++++++++ web/src/components/guide/desk/DeskCue.tsx | 39 ++ web/src/components/guide/desk/DeskStage.tsx | 599 +++++++++++++++++ .../guide/desk/ai/PracticeAi.module.css | 271 ++++++++ .../components/guide/desk/ai/PracticeAi.tsx | 351 ++++++++++ .../components/guide/desk/ai/answers.test.ts | 113 ++++ web/src/components/guide/desk/ai/answers.ts | 165 +++++ web/src/components/guide/desk/ai/headers.ts | 493 ++++++++++++++ web/src/components/guide/desk/apps.ts | 15 + web/src/components/guide/desk/apps/ai.tsx | 14 + .../components/guide/desk/apps/community.tsx | 19 + .../guide/desk/community/CommunityApp.tsx | 197 ++++++ .../components/guide/desk/community/Dock.tsx | 318 +++++++++ .../guide/desk/community/Practice.module.css | 256 ++++++++ .../guide/desk/community/SendDialog.tsx | 121 ++++ .../components/guide/desk/community/data.ts | 80 +++ .../components/guide/desk/community/handle.ts | 50 ++ .../components/guide/desk/community/live.ts | 261 ++++++++ .../components/guide/desk/community/pages.tsx | 608 ++++++++++++++++++ .../components/guide/desk/community/parts.tsx | 381 +++++++++++ .../guide/desk/community/state.test.ts | 63 ++ .../components/guide/desk/community/state.ts | 146 +++++ .../components/guide/desk/community/words.ts | 111 ++++ web/src/components/guide/desk/desk.test.ts | 78 +++ web/src/components/guide/desk/deskStore.ts | 114 ++++ web/src/components/guide/desk/pointer.ts | 380 +++++++++++ web/src/components/guide/desk/query.ts | 44 ++ web/src/components/guide/desk/runner.test.ts | 284 ++++++++ web/src/components/guide/desk/runner.ts | 587 +++++++++++++++++ web/src/components/guide/desk/script.ts | 55 ++ web/src/components/guide/desk/target.ts | 120 ++++ web/src/components/guide/desk/types.ts | 189 ++++++ web/src/components/guide/desk/words.ts | 55 ++ web/src/components/guide/pages.test.ts | 17 +- web/src/components/guide/scenes.ts | 57 +- web/src/components/guide/scenes/community.ts | 69 +- web/src/components/guide/scenes/lab.ts | 77 +-- web/src/components/guide/store.ts | 5 +- .../components/guide/tutorials/community.ts | 289 +++++++++ .../components/guide/tutorials/lab.test.ts | 156 +++++ web/src/components/guide/tutorials/lab.ts | 556 ++++++++++++++++ 51 files changed, 8590 insertions(+), 876 deletions(-) create mode 100644 web/src/components/guide/desk/Desk.module.css create mode 100644 web/src/components/guide/desk/DeskCue.tsx create mode 100644 web/src/components/guide/desk/DeskStage.tsx create mode 100644 web/src/components/guide/desk/ai/PracticeAi.module.css create mode 100644 web/src/components/guide/desk/ai/PracticeAi.tsx create mode 100644 web/src/components/guide/desk/ai/answers.test.ts create mode 100644 web/src/components/guide/desk/ai/answers.ts create mode 100644 web/src/components/guide/desk/ai/headers.ts create mode 100644 web/src/components/guide/desk/apps.ts create mode 100644 web/src/components/guide/desk/apps/ai.tsx create mode 100644 web/src/components/guide/desk/apps/community.tsx create mode 100644 web/src/components/guide/desk/community/CommunityApp.tsx create mode 100644 web/src/components/guide/desk/community/Dock.tsx create mode 100644 web/src/components/guide/desk/community/Practice.module.css create mode 100644 web/src/components/guide/desk/community/SendDialog.tsx create mode 100644 web/src/components/guide/desk/community/data.ts create mode 100644 web/src/components/guide/desk/community/handle.ts create mode 100644 web/src/components/guide/desk/community/live.ts create mode 100644 web/src/components/guide/desk/community/pages.tsx create mode 100644 web/src/components/guide/desk/community/parts.tsx create mode 100644 web/src/components/guide/desk/community/state.test.ts create mode 100644 web/src/components/guide/desk/community/state.ts create mode 100644 web/src/components/guide/desk/community/words.ts create mode 100644 web/src/components/guide/desk/desk.test.ts create mode 100644 web/src/components/guide/desk/deskStore.ts create mode 100644 web/src/components/guide/desk/pointer.ts create mode 100644 web/src/components/guide/desk/query.ts create mode 100644 web/src/components/guide/desk/runner.test.ts create mode 100644 web/src/components/guide/desk/runner.ts create mode 100644 web/src/components/guide/desk/script.ts create mode 100644 web/src/components/guide/desk/target.ts create mode 100644 web/src/components/guide/desk/types.ts create mode 100644 web/src/components/guide/desk/words.ts create mode 100644 web/src/components/guide/tutorials/community.ts create mode 100644 web/src/components/guide/tutorials/lab.test.ts create mode 100644 web/src/components/guide/tutorials/lab.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index eb34f42c..9742df1b 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, 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, 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. 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/web/src/components/guide/Extras.tsx b/web/src/components/guide/Extras.tsx index e78c92a9..b029277c 100644 --- a/web/src/components/guide/Extras.tsx +++ b/web/src/components/guide/Extras.tsx @@ -373,12 +373,27 @@ export default function Extras({ kind, lang, step }: { kind: Extra; lang: GuideL return ; case "consoleTour": return ; - // The make page: 05 Community and 06 Pattern Lab. + // The make page: 05 Community and 06 Pattern Lab. On a screen with the + // desk (desk/DeskStage) the real thing is beside the card, so these — + // screenshots, and the Lab in a tab — are for screens without it + // (Guide.module.css .offDesk). case "communityShots": - return ; + return ( +
+ +
+ ); case "labShots": - return ; + return ( +
+ +
+ ); case "labWindow": - return ; + return ( +
+ +
+ ); } } diff --git a/web/src/components/guide/Guide.module.css b/web/src/components/guide/Guide.module.css index 737e9086..16a8447d 100644 --- a/web/src/components/guide/Guide.module.css +++ b/web/src/components/guide/Guide.module.css @@ -1093,3 +1093,118 @@ line-height: 1.5; color: var(--g-ink-2); } + +/* ── the make page (/guide/make): a desk of windows, or no stage at all ── + + On a screen big enough for the desk (desk/query.ts; the same query is in + desk/Desk.module.css — change all three together) the story is a column on + the right and the desk takes the rest: the real Pattern Lab and the + practice windows, which show what the cards say. The cards' screenshots + are for screens without the desk (.offDesk). Below it there is no stage: + one column of cards, screenshots in them. The first page is untouched. */ + +@media (min-width: 1340px) and (min-height: 640px), (min-width: 1280px) and (min-height: 660px), (min-width: 1180px) and (min-height: 700px) { + .page[data-page="make"] { + /* The story's column; the desk is everything left of it. */ + --desk-col: clamp(330px, 27vw, 410px); + } + .page[data-page="make"] .story { + width: calc(var(--desk-col) + 48px); + margin-left: auto; + } + .page[data-page="make"] .opening, + .page[data-page="make"] .next { + padding: 104px 24px 64px; + } + .page[data-page="make"] .openingInner, + .page[data-page="make"] .nextInner, + .page[data-page="make"] .card { + width: 100%; + } + .page[data-page="make"] .openingTitle { + font-size: clamp(46px, 4.4vw, 66px); + } + .page[data-page="make"] .openingLede { + font-size: 16px; + } + .page[data-page="make"] .chapterHead { + min-height: 72vh; + padding: 104px 24px 32px; + } + .page[data-page="make"] .chapterNum { + font-size: clamp(110px, 11vw, 170px); + } + .page[data-page="make"] .chapterTitle { + font-size: clamp(36px, 3.3vw, 50px); + } + .page[data-page="make"] .chapterLede { + font-size: 16px; + } + .page[data-page="make"] .step { + padding: 10vh 24px 8vh; + } + .page[data-page="make"] .card { + padding: 22px 22px 20px; + } + .page[data-page="make"] .stepTitle { + font-size: 23px; + } + .page[data-page="make"] .body { + font-size: 15px; + line-height: 1.6; + } + .page[data-page="make"] .offDesk { + display: none; + } +} + +/* The desk query's opposite, spelled out (a query list can't be negated as a whole). */ +@media (max-width: 1179.98px), (max-height: 639.98px), (max-width: 1279.98px) and (max-height: 699.98px), (max-width: 1339.98px) and (max-height: 659.98px) { + /* No stage behind: the cards pass under the top bar, which fades them out. */ + .page[data-page="make"] .top { + background: linear-gradient(rgba(10, 9, 8, 0.94) 55%, rgba(10, 9, 8, 0)); + } + .page[data-page="make"] .story { + max-width: 640px; + margin: 0 auto; + } + .page[data-page="make"] .opening, + .page[data-page="make"] .next { + justify-content: center; + padding: 112px 24px 72px; + } + .page[data-page="make"] .openingInner, + .page[data-page="make"] .nextInner, + .page[data-page="make"] .card { + width: 100%; + } + .page[data-page="make"] .chapterHead { + min-height: 60vh; + align-items: flex-start; + text-align: left; + padding: 104px 24px 28px; + } + .page[data-page="make"] .step { + min-height: 90vh; + justify-content: center; + padding: 8vh 24px; + } +} + +@media (max-width: 899px) { + .page[data-page="make"] .opening, + .page[data-page="make"] .next { + align-items: center; + padding: 88px 16px 40px; + } + .page[data-page="make"] .chapterHead { + min-height: 56vh; + justify-content: flex-end; + padding: 88px 16px 24px; + } + .page[data-page="make"] .step { + min-height: 88vh; + align-items: center; + padding: 6vh 16px; + } +} diff --git a/web/src/components/guide/GuideExperience.tsx b/web/src/components/guide/GuideExperience.tsx index e152b8e3..88122e44 100644 --- a/web/src/components/guide/GuideExperience.tsx +++ b/web/src/components/guide/GuideExperience.tsx @@ -10,13 +10,22 @@ import { useGuideStore, type GuideLang, type GuidePageId } from "./store"; import Extras from "./Extras"; import { hereFor, reportUrl } from "./report"; import { PAGES, pagePath, scriptMismatches, type PageChapter } from "./pages"; +import { useDeskFits } from "./desk/query"; +import DeskCue from "./desk/DeskCue"; // A page of the guide (pages.ts: /guide is "start", /guide/make is "make"). // A fixed stage behind, the story scrolling over it. The tracker below finds // the step block nearest the middle of the viewport and hands its scene and // index to the store; the stage takes it from there, from this page's script. +// +// The first page's stage is the 3D device (stage/GuideCanvas). The make +// page's is a desk of app windows left of the story (desk/DeskStage) — the +// real Pattern Lab, a practice community, a practice AI, and a pointer that +// shows the way — on a screen big enough for it (desk/query.ts); below that +// it has no stage, and its cards show screenshots instead. const GuideCanvas = dynamic(() => import("./stage/GuideCanvas"), { ssr: false }); +const DeskStage = dynamic(() => import("./desk/DeskStage"), { ssr: false }); function useScrollTracker(root: React.RefObject) { useEffect(() => { @@ -139,6 +148,7 @@ function Step({

))} {copy.warn &&

{copy.warn}

} + {page === "make" && } {copy.extra && } {copy.note && (