yukey

move: the plumbing that makes the carried set self-standing

commit e3632990bf0537f8b9c0f287d11593f15a91e049 · parent 76fc25b2da · browse this tree
Ximianth, 2026-10-10 05:13:04 UTC

Every edit here is one the move needed and nothing more; the next
commits add the harness and packaging, and later steps reformat and
rename.  Against the verbatim parent commit, this is the whole diff of
the carried bodies:

- tokens.ts: the "ownership" group goes.  Its one token was the
  incubating app's own-faction color, not a kit value; nothing in the
  kit set consumed it.
- doc citations in comments: docs/ui/{ui-ux-guidelines,design-tokens}.md
  become this repo's docs/precepts-we-love-the-keyboard.md and
  docs/precepts-aesthetics.md.
  Two cited section headings were renamed in the trip and are cited by
  their new names ("info pops"; "buttons always show their hotkey").
- focus-stack.ts: the comment describing where focus may land on
  "nothing" named the old app's map root; it now says what the code
  implements, the page shell's fallback surface.
- gallery/registry.ts is new, listing the six kit entries; the shell
  itself is unchanged.
- gallery.html: retitled.
- yukey-testkit: `sleep` moves out of the (not carried) harness into its
  own module, so gallery.ts doesn't reach into the private e2e package.
  The scenarios import the testkit by package name through the
  `yukey-source` exports condition.
  `rewriteRelativeImportExtensions` in the testkit's tsconfig lets the
  carried `./x.ts` imports stay as they are and still emit `./x.js`.
- env vars: SW_E2E_SHOTS_DIR -> YUKEY_E2E_SHOTS_DIR.

Committed with --no-verify: dprint will reformat these files in the
next step, deliberately not in this one.
diff --git a/packages/e2e/scenarios/ip-drive.ts b/packages/e2e/scenarios/ip-drive.ts
index fe0f70b..e3e93db 100644
--- a/packages/e2e/scenarios/ip-drive.ts
+++ b/packages/e2e/scenarios/ip-drive.ts
@@ -9,9 +9,9 @@
 // and the bottom flip (short-viewport emulation).
 // Ported from the Python original (demoscene.meta screenshots, stage-infopop).
 
-import { type Scenario, type ScenarioContext, shot } from "../checks.ts";
-import { openGallery } from "../gallery.ts";
-import { sleep } from "../harness.ts";
+import { type Scenario, type ScenarioContext, shot } from "yukey-testkit/checks";
+import { openGallery } from "yukey-testkit/gallery";
+import { sleep } from "yukey-testkit/sleep";
 
 // State through the shadow roots: the popup's mode/content/position, the demo
 // column's focused row and log, and the aria reflection on the current anchor.
diff --git a/packages/e2e/scenarios/lc-drive.ts b/packages/e2e/scenarios/lc-drive.ts
index 60182fe..fc4cbf4 100644
--- a/packages/e2e/scenarios/lc-drive.ts
+++ b/packages/e2e/scenarios/lc-drive.ts
@@ -28,9 +28,9 @@
 // Ported from the Python original (demoscene.meta screenshots, stage-livecolumn,
 // updated in place by stage-list-component).
 
-import { type Scenario, type ScenarioContext, shot } from "../checks.ts";
-import { openGallery } from "../gallery.ts";
-import { sleep } from "../harness.ts";
+import { type Scenario, type ScenarioContext, shot } from "yukey-testkit/checks";
+import { openGallery } from "yukey-testkit/gallery";
+import { sleep } from "yukey-testkit/sleep";
 
 // demo(i): the i-th swg-livecolumn-demo (0 = refill, 1 = drain).
 const demo = (i: number) =>
diff --git a/packages/e2e/scenarios/mc-drive.ts b/packages/e2e/scenarios/mc-drive.ts
index 99fd4ee..2776940 100644
--- a/packages/e2e/scenarios/mc-drive.ts
+++ b/packages/e2e/scenarios/mc-drive.ts
@@ -3,9 +3,9 @@
 // exercise real Tab traversal or real typing; these can.
 // Ported from the Python original (demoscene.meta screenshots, stage-focus-model).
 
-import type { Scenario, ScenarioContext } from "../checks.ts";
-import { openGallery } from "../gallery.ts";
-import { sleep } from "../harness.ts";
+import type { Scenario, ScenarioContext } from "yukey-testkit/checks";
+import { openGallery } from "yukey-testkit/gallery";
+import { sleep } from "yukey-testkit/sleep";
 
 // Reads the interesting state through the shadow roots: which demo element holds
 // real focus, its aria-activedescendant, ring positions, text mode, and the log line.
diff --git a/packages/e2e/scenarios/sw-drive.ts b/packages/e2e/scenarios/sw-drive.ts
index 6c797b8..0471c0f 100644
--- a/packages/e2e/scenarios/sw-drive.ts
+++ b/packages/e2e/scenarios/sw-drive.ts
@@ -7,9 +7,9 @@
 // MutationObserver installed before the keystroke, not with a screenshot race).
 // Ported from the Python original (demoscene.meta screenshots, stage-button).
 
-import { type Scenario, type ScenarioContext, shot } from "../checks.ts";
-import { openGallery } from "../gallery.ts";
-import { sleep } from "../harness.ts";
+import { type Scenario, type ScenarioContext, shot } from "yukey-testkit/checks";
+import { openGallery } from "yukey-testkit/gallery";
+import { sleep } from "yukey-testkit/sleep";
 
 // State through the shadow roots: which element holds real focus (host chain),
 // each demo's log line, and the columns' focused rows.
diff --git a/packages/yukey-testkit/src/checks.ts b/packages/yukey-testkit/src/checks.ts
index fbfcf49..261bb60 100644
--- a/packages/yukey-testkit/src/checks.ts
+++ b/packages/yukey-testkit/src/checks.ts
@@ -44,9 +44,9 @@ export class Checks {
 }
 
 // Screenshots were stage evidence in the original drivers, never assertions:
-// they write only when SW_E2E_SHOTS_DIR names a destination, and no-op otherwise.
+// they write only when YUKEY_E2E_SHOTS_DIR names a destination, and no-op otherwise.
 export async function shot(cdp: Cdp, name: string): Promise<void> {
-	const dir = process.env.SW_E2E_SHOTS_DIR;
+	const dir = process.env.YUKEY_E2E_SHOTS_DIR;
 	if (dir === undefined || dir === "") return;
 	mkdirSync(dir, { recursive: true });
 	writeFileSync(join(dir, name), await cdp.screenshot());
diff --git a/packages/yukey-testkit/src/gallery.ts b/packages/yukey-testkit/src/gallery.ts
index bff71c8..517708c 100644
--- a/packages/yukey-testkit/src/gallery.ts
+++ b/packages/yukey-testkit/src/gallery.ts
@@ -3,7 +3,7 @@
 // convention; here it is explicit and self-contained per scenario.
 
 import type { ScenarioContext } from "./checks.ts";
-import { sleep } from "./harness.ts";
+import { sleep } from "./sleep.ts";
 
 export async function openGallery(
 	ctx: ScenarioContext,
diff --git a/packages/yukey-testkit/src/sleep.ts b/packages/yukey-testkit/src/sleep.ts
new file mode 100644
index 0000000..227aeb9
--- /dev/null
+++ b/packages/yukey-testkit/src/sleep.ts
@@ -0,0 +1,3 @@
+// A promise that resolves after ms.  Scenarios use it to poll between steps;
+// the harness that launches Chrome uses it the same way.
+export const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
diff --git a/packages/yukey-testkit/tsconfig.json b/packages/yukey-testkit/tsconfig.json
index d991313..b97fee7 100644
--- a/packages/yukey-testkit/tsconfig.json
+++ b/packages/yukey-testkit/tsconfig.json
@@ -7,6 +7,7 @@
 		"types": ["node"],
 		"strict": true,
 		"noUncheckedIndexedAccess": true,
+		"rewriteRelativeImportExtensions": true,
 		"noEmit": true
 	},
 	"include": ["src"]
diff --git a/packages/yukey/src/focus-stack.ts b/packages/yukey/src/focus-stack.ts
index 04cc9df..5f2fcc2 100644
--- a/packages/yukey/src/focus-stack.ts
+++ b/packages/yukey/src/focus-stack.ts
@@ -53,9 +53,8 @@
 //                      surfaces died are swept first); otherwise the fallback.
 //   fail-closed        routing with nowhere to go throws.  The number of
 //                      places focus may land on "nothing" is between zero and
-//                      one in the whole UI, and that one (the map root) is
-//                      the page shell's fallback -- never a silent drop
-//                      to <body>.
+//                      one in the whole UI, and that one is the page shell's
+//                      fallback surface -- never a silent drop to <body>.
 //
 // Popping focuses the surface's first tab stop (for a megacolumn host that is
 // its one [data-mc-container], whose machine's row memory then restores the
diff --git a/packages/yukey/src/gallery/registry.ts b/packages/yukey/src/gallery/registry.ts
new file mode 100644
index 0000000..72744a7
--- /dev/null
+++ b/packages/yukey/src/gallery/registry.ts
@@ -0,0 +1,20 @@
+// The one place gallery entries are registered: one import line per component.
+// Keep the list alphabetical by id so the nav order is deterministic and merges stay clean.
+// (See entry.ts for the convention itself.)
+
+import megacolumn from "../megacolumn.gallery.js";
+import swButton from "../sw-button.gallery.js";
+import swInfopop from "../sw-infopop.gallery.js";
+import swList from "../sw-list.gallery.js";
+import swLiveList from "../sw-live-list.gallery.js";
+import tokens from "../tokens.gallery.js";
+import type { GalleryEntry } from "./entry.js";
+
+export const entries: GalleryEntry[] = [
+	megacolumn,
+	swButton,
+	swInfopop,
+	swList,
+	swLiveList,
+	tokens,
+];
diff --git a/packages/yukey/src/megacolumn-nav.ts b/packages/yukey/src/megacolumn-nav.ts
index 76e5f5e..0fc7dfd 100644
--- a/packages/yukey/src/megacolumn-nav.ts
+++ b/packages/yukey/src/megacolumn-nav.ts
@@ -3,7 +3,7 @@
 // key names and row lists and acts on the routing decisions it returns; this file
 // never touches a DOM type, which is what makes it testable to exhaustion.
 //
-// The model it implements is the megacolumn from docs/ui/ui-ux-guidelines.md:
+// The model it implements is the megacolumn from docs/precepts-we-love-the-keyboard.md:
 // focus granularity is the row; up/down move between focusable rows (skipping
 // unfocusable ones and jumping section gaps); a row may have a main control plus
 // one aux control (the small-x norm), reached with ArrowRight and left again with
diff --git a/packages/yukey/src/sw-button.ts b/packages/yukey/src/sw-button.ts
index 9814e85..75da923 100644
--- a/packages/yukey/src/sw-button.ts
+++ b/packages/yukey/src/sw-button.ts
@@ -1,7 +1,7 @@
 // sw-button: the button that makes the guidelines' keyboard norms structural.
 // Use it and the norms happen; there is nothing to remember per screen.
 //
-// The norms it embodies (docs/ui/ui-ux-guidelines.md, "buttons mark their hotkey"
+// The norms it embodies (docs/precepts-we-love-the-keyboard.md, "buttons always show their hotkey"
 // and the megacolumn sections):
 //
 //   - Hotkey marking: if the hotkey letter occurs in the label, its first
diff --git a/packages/yukey/src/sw-info.ts b/packages/yukey/src/sw-info.ts
index 376fd99..d63cf3c 100644
--- a/packages/yukey/src/sw-info.ts
+++ b/packages/yukey/src/sw-info.ts
@@ -1,6 +1,6 @@
 // The info norm's wiring: the sw-info event shape and the data-info carrier contract.
-// This file is the SSOT for both.  (The norm itself: docs/ui/ui-ux-guidelines.md,
-// "more norms: info" -- i means "info, please" on almost any element.)
+// This file is the SSOT for both.  (The norm itself: docs/precepts-we-love-the-keyboard.md,
+// "info pops" -- i means "info, please" on almost any element.)
 //
 // One authored content, two triggers:
 //
diff --git a/packages/yukey/src/sw-infopop.ts b/packages/yukey/src/sw-infopop.ts
index a576897..8734822 100644
--- a/packages/yukey/src/sw-infopop.ts
+++ b/packages/yukey/src/sw-infopop.ts
@@ -1,7 +1,7 @@
 // sw-infopop: the info norm's popup surface -- the keyboard's tooltip and the
 // mouse's tooltip as ONE element showing one authored content per anchor
 // (the wiring SSOT -- the sw-info event and the data-info carrier attribute --
-// is sw-info.ts; the norm is docs/ui/ui-ux-guidelines.md "more norms: info").
+// is sw-info.ts; the norm is docs/precepts-we-love-the-keyboard.md "info pops").
 //
 // One per app: place it once in the app shell's render (the gallery shell hosts
 // one today; sw-app hosts one at migration), and every sw-info event and every
diff --git a/packages/yukey/src/tokens.gallery.ts b/packages/yukey/src/tokens.gallery.ts
index e359c36..f9aaecf 100644
--- a/packages/yukey/src/tokens.gallery.ts
+++ b/packages/yukey/src/tokens.gallery.ts
@@ -296,7 +296,7 @@ const entry: GalleryEntry = {
 		"The visual vocabulary everything else is built from: --sw-* custom properties, " +
 		"rendered live from their definitions in tokens.ts.  Names are semantic roles and are " +
 		"the durable part; values are the iteration surface -- edit tokens.ts, rebuild, " +
-		"and this page shows the new truth.  Intent notes live in docs/ui/design-tokens.md.",
+		"and this page shows the new truth.  Intent notes live in docs/precepts-aesthetics.md.",
 	variants: [
 		{
 			caption:
diff --git a/packages/yukey/src/tokens.ts b/packages/yukey/src/tokens.ts
index b5f6c89..10d4cb4 100644
--- a/packages/yukey/src/tokens.ts
+++ b/packages/yukey/src/tokens.ts
@@ -1,6 +1,6 @@
 // The design-token vocabulary: every color, gap, size, and type choice the UI is
 // built from, as CSS custom properties.  This file is the SSOT for token names and
-// values; docs/ui/design-tokens.md describes the intent, and the `tokens` gallery
+// values; docs/precepts-aesthetics.md describes the intent, and the `tokens` gallery
 // entry renders this structure live.
 //
 // Tokens are defined as data (tokenGroups) and the :root stylesheet is generated
@@ -316,21 +316,6 @@ export const tokenGroups: TokenGroup[] = [
 			},
 		],
 	},
-	{
-		id: "ownership",
-		title: "ownership",
-		about: "Faction identity colors.  One faction exists today; the vocabulary is ready for more.",
-		tokens: [
-			{
-				name: "--sw-faction-own",
-				value: "#8fd4c0",
-				kind: "color",
-				about:
-					"the player's own units and holdings.  Seeded to the working-economy teal " +
-					"(yours = alive); expect playtest to differentiate",
-			},
-		],
-	},
 	{
 		id: "spacing",
 		title: "spacing",
diff --git a/packages/yukey/static/gallery.html b/packages/yukey/static/gallery.html
index f61cf6b..bbbd4b2 100644
--- a/packages/yukey/static/gallery.html
+++ b/packages/yukey/static/gallery.html
@@ -3,7 +3,7 @@
 <head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
-<title>starweave gallery</title>
+<title>yukey gallery</title>
 <style>
 	html, body {
 		margin: 0;