Skip to content

play: typed code shows at once in the spec editor, and a phone keeps it in view - #1359

Merged
gHashTag merged 1 commit into
mainfrom
fix/play-editor-typing-visible
Oct 4, 2026
Merged

gHashTag merged 1 commit into
mainfrom
fix/play-editor-typing-visible

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Closes #1357

What was wrong

The spec editor in the X player (/play/<id>/, public/play/embed.html + embed.js) is a transparent <textarea> over a coloured <pre id="hl">. The <pre> is the only thing a reader sees, and it was redrawn only inside draw(), after RECOMPILE_DEBOUNCE_MS of silence plus a full compile. Each keystroke restarted the timer, so on a phone nothing typed appeared until the typist stopped.

Measured with headless BrowserOS (Chromium) over CDP, phone emulation 390x844, 14 keys typed 120 ms apart at the end of line 15 of hello_world.t27. Each number is the ms until a key first shows in #hl:

build CPU ms per key
live cb612ea99407 1x 0,1670,1548,1427,1306,1183,1059,933,804,678,1792,554,427,300
live cb612ea99407 6x slower 0,1790,486,1535,621,757,1022,1665,1402,1150,1922,2047,891,1274
this PR 7c9449f8a661 1x 0,0,0,0,0,0,0,0,0,0,0,0,0,1
this PR 7c9449f8a661 6x slower 0,0,2,0,0,0,2,0,0,2,0,0,2,2

What changed

  • embed.js: #hl is repainted on every input event. Lines the last compile saw keep its colours (a head/tail line diff against what was painted), changed lines are drawn as plain escaped text, and only the compile is debounced. The textarea's own scrollLeft/scrollTop are pinned to 0, because the pane scrolls, not the field.
  • specs/x/player.t27: IOS_FOCUS_ZOOM_BELOW_PX = 16 and EDITOR_TOUCH_FONT_PX = 16, with the test focusing_the_editor_on_a_phone_does_not_zoom. play-from-spec.mjs requires both in its schema. The player reads EDITOR_TOUCH_FONT_PX from the compiled spec into --ed-touch-font, used under @media (pointer:coarse). The comment on RECOMPILE_DEBOUNCE_MS now says what it delays.
  • embed.html:
    • In portrait, html scrolls and body no longer does (a scrolling body is its own scroll container, and nothing inside it can stick). The header (tabs) and footer (compile verdict) are position:sticky, and scroll-padding keeps the caret's line out from under them.
    • Line numbers are position:sticky; left:0, with box-sizing:border-box on the ::before (* does not match pseudo-elements; without it every code glyph moved 1em right of the caret).
    • The textarea gets wrap="off" and autocorrect="off".
    • -webkit-text-size-adjust:100%.
  • Regenerated: public/play/player.t27 and public/play/hello-world/index.html (player build 7c9449f8a661).

The desktop and X-card layout at 1200x675 is unchanged: compared side by side with the live screenshot, and the glyph-to-caret offset is 0-0.08 px, the same as live.

{
  "version": 1,
  "head_sha": "34e36eca4aad32c0833d63e4b16066c77ce17e83",
  "summary": "Typed code in the X player's spec editor now shows on the next frame instead of after the debounced compile, and on a phone the editor no longer zooms, keeps its tabs and compile verdict in view, and keeps its line numbers.",
  "changes": [
    "embed.js repaints the coloured layer on every input event; lines unchanged since the last compile keep their colours and changed lines are drawn plain until the debounced compile recolours them.",
    "specs/x/player.t27 gains IOS_FOCUS_ZOOM_BELOW_PX and EDITOR_TOUCH_FONT_PX with a test that the touch font is never below the zoom threshold; the player reads the font size from the compiled spec.",
    "embed.html makes the header and footer sticky in portrait by letting html scroll instead of body, adds scroll-padding for the caret, sticky line numbers, wrap=off and autocorrect=off on the textarea.",
    "The generated public/play/player.t27 and hello-world/index.html are rewritten by play-from-spec.mjs; player build 7c9449f8a661."
  ],
  "tests": [
    {
      "command": "node gap.mjs \"$U\" with U = https://t27.ai/play/embed.html?spec=specs%2Fdemos%2Fhello_world.t27&v=cb612ea99407 (live) and the same query on a local server of this branch (390x844 mobile emulation, 14 keys 120 ms apart)",
      "result": "Live build: 0 to 1792 ms per key before a typed key is visible; this branch: 0 to 1 ms per key.",
      "status": "passed",
      "evidence": "invisibleMsPerKey live [0,1670,1548,1427,1306,1183,1059,933,804,678,1792,554,427,300], branch [0,0,0,0,0,0,0,0,0,0,0,0,0,1]"
    },
    {
      "command": "CPU=6 node gap.mjs \"$U\" for the same two URLs",
      "result": "With the CPU throttled six times, live reaches 2047 ms per key and this branch stays at 0 to 2 ms.",
      "status": "passed",
      "evidence": "invisibleMsPerKey live [0,1790,486,1535,621,757,1022,1665,1402,1150,1922,2047,891,1274], branch [0,0,2,0,0,0,2,0,0,2,0,0,2,2]"
    },
    {
      "command": "node align.mjs <url> 1200 675 0 and node align.mjs <url> 390 844 1",
      "result": "Coloured glyphs sit on the textarea's text origin within 0.18 px on mobile and 0.08 px on desktop, the same as live.",
      "status": "passed",
      "evidence": "dx 0 to 0.08 px at 1200x675 and at most 0.18 px at 390x844, identical to the live player's offsets."
    },
    {
      "command": "npm run check:play",
      "result": "The generated player files are up to date and every test block in specs/x/player.t27 holds.",
      "status": "passed",
      "evidence": "player build 7c9449f8a661; 1 play(s): hello-world; spec tests 9, asserts 23, all hold"
    },
    {
      "command": "npm run test:play-spec",
      "result": "All twelve generator and schema tests for the player spec pass on this branch.",
      "status": "passed",
      "evidence": "# pass 12, # fail 0"
    },
    {
      "command": "iOS Simulator, Safari, http://127.0.0.1:8766/play/hello-world/",
      "result": "Not run: the simulator hung on openurl, screenshot and Safari launch, so the iOS-specific changes are not device-verified.",
      "status": "not_run",
      "evidence": "simctl openurl and io screenshot did not return; the simulator was shut down without a result."
    }
  ],
  "limitations": [
    "The 16 px touch font, wrap=off, autocorrect=off and text-size-adjust are iOS fixes taken from WebKit's documented behaviour and were not checked on an iPhone or in the X app's in-app browser.",
    "All timings are from Chromium with mobile emulation; real phone keyboards and IME composition were not exercised.",
    "Between keystrokes and the compile, changed lines are plain text; they take their colours about 250 ms after the typist pauses.",
    "X's player-card rules ask that the player not collect data entry; an editable editor inside the card is a policy question for the owner, not changed here."
  ],
  "tags": ["t27", "XPlayerCard", "MobileWeb", "Editor"],
  "blog": {
    "title": "Why typing in the X player's spec editor showed nothing on a phone",
    "summary": "The editor is a transparent textarea over a coloured layer, and the layer waited for a debounced compile. Repainting it on every keystroke took the gap from up to two seconds to under three milliseconds.",
    "outline": [
      "The pattern: a transparent textarea for the caret and selection over a pre coloured from the compiler's own tokens, so the reader only ever sees the pre.",
      "The anomaly measured: 300 to 1792 ms per key before a typed character appeared, up to 2047 ms with the CPU throttled six times, because each keystroke restarted the compile timer.",
      "The fix: repaint the layer synchronously on input, keep the colours of lines the last compile saw, draw changed lines plain, and debounce only the compile.",
      "The phone details: a 16 px floor so iOS does not zoom the focused field, html as the scroller so the tabs and verdict can stick, and sticky line numbers with border-box sizing."
    ]
  }
}

🤖 Generated with Claude Code

…it in view

The editor is a transparent textarea over a coloured <pre>, and the <pre> was
redrawn only after the debounced compile, so while a reader kept typing nothing
new appeared: 300-1792 ms per key measured on the live player, up to 2047 ms
with the CPU throttled 6x. The <pre> is now repainted on every input event --
lines the last compile saw keep its colours, changed lines are drawn plain --
and only the compile waits. Measured after: 0-2 ms per key.

On a touch screen the editor's text is 16 px (EDITOR_TOUCH_FONT_PX in
specs/x/player.t27, tested against IOS_FOCUS_ZOOM_BELOW_PX) so iOS Safari does
not zoom into the focused field. In portrait the page scrolls instead of body,
so the tabs and the compile verdict stick; line numbers stick when a long line
scrolls the pane; the textarea gets wrap="off" and autocorrect="off".

Closes #1357

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Oct 4, 2026
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

X Brain Health Check

Score: 100.0/100
Status: 🟢 HEALTHY
Threshold: 80/100

X Brain is above merge threshold

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

🧪 Brain Stress Test Results

Tests: 261/261 passed, 0 leaked
Status: 🟢 PASS
Gate: every test in zig build test-brain-stress passes and none leaks

@gHashTag
gHashTag merged commit 8d027f3 into main Oct 4, 2026
38 of 39 checks passed
gHashTag added a commit that referenced this pull request Oct 4, 2026
…issue it closes

Closes #1365

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added status:completed Done and removed status:in-progress 🔵 Agent working labels Oct 4, 2026
github-actions Bot added a commit that referenced this pull request Oct 4, 2026
play: typed code shows at once in the spec editor, and a phone keeps it in view (#1359)

The editor is a transparent textarea over a coloured <pre>, and the <pre> was
redrawn only after the debounced compile, so while a reader kept typing nothing
new appeared: 300-1792 ms per key measured on the live player, up to 2047 ms
with the CPU throttled 6x. The <pre> is now repainted on every input event --
lines the last compile saw keep its colours, changed lines are drawn plain --
and only the compile waits. Measured after: 0-2 ms per key.

On a touch screen the editor's text is 16 px (EDITOR_TOUCH_FONT_PX in
specs/x/player.t27, tested against IOS_FOCUS_ZOOM_BELOW_PX) so iOS Safari does
not zoom into the focused field. In portrait the page scrolls instead of body,
so the tabs and the compile verdict stick; line numbers stick when a long line
scrolls the pane; the textarea gets wrap="off" and autocorrect="off".

Closes #1357

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Oct 4, 2026
… a post (#1366)

* play: a terminal for the seven t27 backends, one share page each, and a post

The X player's spec editor gains a terminal. Every word it prints, every
command it knows and every backend fact lives in specs/x/player.t27;
public/play/shell.js only interprets those constants.

- 12 commands; `t27c gen <backend>` for zig, c, rust, verilog,
  verilog-hir, js, ts, with the `gen-<backend>` spelling as an alias.
- `run` executes only the JS module in the browser (RUNS_IN_BROWSER);
  the other six print the native command to run instead of pretending.
- One share page per backend under play/hello-world/<backend>/ with its
  own card, generated by scripts/play-from-spec.mjs from the spec.
- .gitignore: `zig/` was ignored repo-wide, which silently dropped the
  zig page; negated for public/play/*/zig/.
- Recording t27.ai/term/play-terminal-gates/ (check:play and
  test:play-spec, 21 pass, 0 fail) and the bilingual post
  a-terminal-for-seven-backends-in-an-x-post.

Gates: check:play (spec tests 14, asserts 43, all hold),
test:play-spec (21 pass, 0 fail), typecheck ratchet at baseline.

Closes #1365

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

* blog: the terminal post names its own PR, trinity#1366

Closes #1365

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

* blog: the terminal post's receipts say #1359 merged and #1365 is the issue it closes

Closes #1365

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Oct 4, 2026
play: a terminal for the seven t27 backends, one share page each, and a post (#1366)

* play: a terminal for the seven t27 backends, one share page each, and a post

The X player's spec editor gains a terminal. Every word it prints, every
command it knows and every backend fact lives in specs/x/player.t27;
public/play/shell.js only interprets those constants.

- 12 commands; `t27c gen <backend>` for zig, c, rust, verilog,
  verilog-hir, js, ts, with the `gen-<backend>` spelling as an alias.
- `run` executes only the JS module in the browser (RUNS_IN_BROWSER);
  the other six print the native command to run instead of pretending.
- One share page per backend under play/hello-world/<backend>/ with its
  own card, generated by scripts/play-from-spec.mjs from the spec.
- .gitignore: `zig/` was ignored repo-wide, which silently dropped the
  zig page; negated for public/play/*/zig/.
- Recording t27.ai/term/play-terminal-gates/ (check:play and
  test:play-spec, 21 pass, 0 fail) and the bilingual post
  a-terminal-for-seven-backends-in-an-x-post.

Gates: check:play (spec tests 14, asserts 43, all hold),
test:play-spec (21 pass, 0 fail), typecheck ratchet at baseline.

Closes #1365

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

* blog: the terminal post names its own PR, trinity#1366

Closes #1365

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

* blog: the terminal post's receipts say #1359 merged and #1365 is the issue it closes

Closes #1365

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

X player: typed code is invisible in the spec editor on a phone

1 participant