Skip to content

chore: synchronize dev with main - #211

Merged
cervantesh merged 197 commits into
devfrom
main
Jul 20, 2026
Merged

chore: synchronize dev with main#211
cervantesh merged 197 commits into
devfrom
main

Conversation

@cervantesh

@cervantesh cervantesh commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • restore dev as the integration branch without rewriting history
  • synchronize the 197 commits already merged directly to main
  • enable subsequent parity slices to target dev

Safety

  • no force push or branch deletion
  • CI is allowed to run
  • releases remain tag-only (v*)

cervantesh and others added 30 commits July 15, 2026 14:23
Promote to production: Windows ANSI color advertising
Two DDD leaks confirmed by adversarial review shared one root: core exposed
the data but the *rule* that interprets a row of cells lived duplicated in
each call site. Two helpers in core now own each rule.

#4 core.RowText is the one canonical "text of a row" rule (skip Rune==0 and
WideContinuation, emit rune+combining, trim trailing blanks). selection and
App.Line (term:line) both defer to it, deleting two hand-copied variants.
This also fixes term:line(): it used to turn Rune==0 padding into a written
space and keep WideContinuation, so a row could copy as "AB" but script as
"A B"; both now agree. Reachability of that divergence is low (Clear/blank
use Rune==' '), but the single source of truth guarantees consistency.

#8 Terminal.ViewportTopGlobalRow / GlobalRowToViewport own the
"scrollbackRows - displayOffset" arithmetic. CopyView, LineWrapped, search
scroll-to-match and the draw highlight consume them instead of each
re-deriving the formula by hand. Pure refactor, no behavior change.

Tests: RowText contract (interior Rune==0 hole, wide glyph, trailing trim,
combining, all-blank), viewport mapping incl. zero/mid/max display offset and
off-window guards, and App.Line == core.RowText equivalence over a viewport.
All green: go build ./... , go build -tags glfw ./... , go test ./... , and the
glfw-tagged glfwgl package.

Reviewed cross-engine (gpt-5.6-sol): approve_with_nits; both nits (App.Line
test, max-offset viewport case) applied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(core): regla canónica única para texto de fila y mapeo de viewport (DDD #4/#8)
…capsulate default-color rule

Three low-risk DDD backlog items from the adversarial analysis (all
behavior-preserving; verified go build/test green with and without the glfw tag,
cross-reviewed by gpt-5.6-sol: approve, no findings).

#2 The 16-color ANSI ramp lived byte-for-byte in both core.ansi16 (the active
source the SGR parser resolves against) and theme.DefaultPalette().ANSI (no
production consumer). theme now derives its ramp from core.ANSIColor in a loop —
one source of truth, no drift. Verified the derived values match the old literal
byte-for-byte. theme->core is presentation->domain (no cycle).

#2 The renderer decided "is this the terminal default color?" by comparing cells
against the concrete core.DefaultBG/DefaultFG RGB in three sites. That rule now
lives in the domain as Attr.HasExplicitBG/HasExplicitFG; app_row.go asks instead
of hard-coding the comparison. Same comparison, just encapsulated. A fuller fix
(an explicit default flag on Attr) is deferred — it changes the value-object
layout and touches the parser, for low payoff.

#7 selection no longer imports render: Text/clamp/clampPoint/lineText take
(cells []core.Cell, cols, rows) instead of render.Snapshot, so the domain logic
depends only on core. The single caller passes snap.Cells/Cols/Rows. Selection
and copy output are unchanged; the package test was moved off render too.

#9 Documented ApplicationKeypadMode as parsed-and-stored-but-unconsumed: the
input encoder models no numeric-keypad keys yet, so wiring the mode is a new
feature, not a bug fix. Comment only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(ddd): dedup paleta ANSI, desacoplar selection de render, encapsular sentinela (backlog #2/#7/#9)
Bounded version of DDD finding #1 (god-object App). App mixed 8+ contexts as
~70 flat fields; four cohesive ones are now named structs, extending the pattern
App already used (status/overlays/zoom/link/damage). Pure field regrouping plus
access-site renames — zero behavior change, all fields main-thread only.

- searchState (app_search.go): search.{active,query,hasMatch,matchRow,matchCol,
  matchLen,viewRow}
- selectionState (mouse.go): selection.{dragging,active,start,end}
- mouseReportState (mouse.go): mouseReport.{down,button,mods}
- hudCache (app_draw.go): hud.{lines,colors,notice,showStats,cols,rows,statsAt}

The live showStats toggle stays a top-level field, distinct from the cached
hud.showStats used for HUD memoization (verified the comparison
showStats == hud.showStats is preserved). app.go drops from 493 to 470 lines
(under the 500 gate) and sheds now-unused imports (image/color, selection).

~121 access sites migrated across 7 files; grep confirms no old a.<field> name
remains. Cross-reviewed by gpt-5.6-sol (approve, no findings). Green:
go build/test ./... and the glfw-tagged glfwgl package + go vet.

Not done: the deeper controller extraction (behavior-carrying methods off App) —
the whole review panel deprioritized it as high-churn for modest payoff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(glfwgl): agrupar sub-estados cohesivos de App en structs con nombre (DDD #1 acotado)
…ccessor

Two low-risk DDD cleanups (behavior-preserving; cross-reviewed by gpt-5.6-sol:
approve, no findings).

#6 The DECSCUSR cursor style flowed as a raw int (parser -> core -> snapshot ->
renderer) with magic numbers 1..6 switched on in the renderer and the blink loop.
It is now a core.CursorStyle type with named constants and two domain methods —
Shape() (block/underline/bar, or default for style 0) and Blink() (blink, ok) —
so the "which style blinks / what shape" rule lives in the domain. drawCursor and
blinkActive call the methods instead of switching on integers; behavior is
identical (verified the 0..6 + unknown mapping).

D2 Terminal.Cells() returned the live internal cell slice and had no production
caller — only tests, which could mutate terminal state behind its invariants. It
is removed; the 14 test sites use a copyCells helper (make + CopyView, a
defensive copy). No test mutated the returned slice, so the copy is equivalent.

Green: go build/test ./... , go build/test -tags glfw for glfwgl, go vet. New
test TestCursorStyleShapeAndBlink covers all seven styles plus an unknown value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(core): tipar cursor style (DECSCUSR) y eliminar Cells() muerto (DDD #6/D2)
…t deps

Deep half of DDD #1, scoped to the one context that decouples cleanly (per the
review panel's read that mouse/selection/HUD only relocate coupling). The seven
search methods move off *App onto *searchController, which owns the search state
plus its three App services as explicit, injected dependencies — the terminal to
query, the shared mutex guarding it against the PTY reader, and a redraw signal —
rather than a back-pointer to the whole App.

The mutex dependency is &app.mu (the same object App locks elsewhere), wired via
searchController.init() after construction and before the render loop starts, so
no search method can run with nil deps. scrollGlobalRowIntoView stays lock-free
and is called with the mutex held (from next() and from App.Search). Behavior is
identical; the field-read sites (draw/damage/row) keep working since the
controller keeps the fields.

Payoff: search logic is co-located and unit-testable with a real core.Terminal
and no App/window/GL — see search_controller_test.go (find/miss, empty-query
no-op, query editing, open/close). Cross-reviewed by gpt-5.6-sol: approve, no
findings. Green: go build/test with and without the glfw tag, go vet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(glfwgl): extraer searchController con dependencias explícitas (DDD #1 profundo, solo search)
The pixel<->cell math (padding, cell size, grid extent) lived on App as
pointFromPixels reading scattered fields. It is now a plain gridMetrics value
with a cellAt method, in a file with no build tag so the geometry is testable
without the glfw toolchain. pointFromPixels is a thin wrapper over
a.metrics().cellAt; mouse and selection point mapping go through it unchanged.
This removes the geometry coupling to App for that math (step 1 of decoupling
mouse/selection). Behavior identical; grid_metrics_test covers interior, both
clamps, and edges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
searchController no longer holds a raw *sync.Mutex and *core.Terminal; it depends
on a narrow searchTerminal port (one atomic SearchUpward) and a redraw signal.
The new lockedTerminal adapter owns the terminal plus the mutex that guards it
against the PTY reader and does all the locking internally, so the controller
never handles a mutex. SearchUpward runs from-row computation, SearchBackward,
and scroll-into-view in one critical section (identical to the old inline lock);
scrollGlobalRowIntoView is now a free function called under that lock. App holds
one lockedTerminal (mu is &App.mu — the same object every other App term access
locks) and App.Search (Lua) uses it too. Search state is main-thread only, so
recording the match outside the lock is safe.

Payoff: the controller is testable with a fake searchTerminal — no terminal, no
lock (search_controller_test). This is step 2 of decoupling App; the same port
pattern can absorb the remaining term-access sites later. Behavior identical;
cross-reviewed by gpt-5.6-sol: approve, no findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(glfwgl): gridMetrics (geometría) + lockedTerminal port (lock) — desacoplar App
…atus band)

Step 3 of decoupling the frontend, done the low-impact way. The three chrome
drawers mixed layout logic with GL emission. Layout now lives in pure functions
(hudLayout/searchBarLayout/statusBandLayout in draw_list.go, no build tag) that
return a []drawCmd; a paint executor emits the list. This follows the repo's
existing idiom (runs.go/scale.go: pure logic, no tag, tested) and gets the
testability without touching the hot grid path or adding per-primitive virtual
dispatch — paint runs once per chrome per frame.

No visual change: the draw commands are a faithful transcription of the old
inline drawing (verified byte-for-byte). Fragile invariants preserved and
tested: the status text baseline is y=by WITHOUT the pad offset (existing
asymmetry), BLEND stays enabled on exit (no cleanup Disable), the HUD box width
is measured in runes, and the muted search color applies only to a non-empty
query with no match. Text layout is pure because drawString advances a fixed
cellW per rune (monospace) and never consults atlas metrics.

Also removed leftover DBG log.Printf calls and the dead sampleW/cachedRune('S')
block from app_status.go (and the now-unused log import).

13 layout tests run without the glfw tag. Architect spec + adjudication by Fable;
cross-reviewed by gpt-5.6-sol (approve, no findings). Green: go build/test with
and without -tags glfw, go vet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(glfwgl): capa draw-list para las chrome (HUD/search/status) — paso 3 desacople
Scaffolding for alternate GPU backends. New internal/frontend/gpu package:
- Renderer: the backend-neutral per-frame contract (Resize, BeginFrame/clear,
  FillRect, DrawGlyph, UploadAtlasPage, EndFrame, Destroy). First-draft shape.
- vulkan.go (//go:build vulkan): vulkanRenderer STUB — the standard Vulkan init
  laid out as a phase-by-phase skeleton (createInstance … createSyncObjects,
  recreateSwapchain) plus stubbed Renderer methods with per-step TODOs. No
  external deps yet; returns errNotImplemented.
- metal.go (//go:build metal && darwin): metalRenderer STUB — same shape, pure
  Go (no cgo) so it cross-compiles for review; the real backend needs a
  CGo/Obj-C bridge and a Mac.
- doc.go: the plan, incl. the Phase 0 prerequisite — route the whole frontend
  (grid hot path, not just the chrome draw-list) through gpu.Renderer and add an
  OpenGL adapter first; only then do these backends render the terminal.

Build-tag isolated: default and -tags glfw builds are unchanged. Verified:
go build ./... , -tags glfw ./... , -tags vulkan ./internal/frontend/gpu/ (+vet),
and GOOS=darwin -tags metal cross-compile all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(gpu): seam Renderer + stubs Vulkan/Metal (scaffolding)
Refine the backend-neutral gpu.Renderer contract and add a faithful OpenGL
implementation, without yet wiring it into the frontend (dead code this commit;
PR-B routes the render path through it).

Interface changes (driven by reading the real hot path):
- Split BeginFrame(w,h) (establish top-left pixel space, no clear) from a
  separate Clear(c) — the frontend keeps a partial-redraw damage model and must
  clear only on a full redraw; a clearing BeginFrame would erase undamaged rows.
- GlyphMode {Mask, Color, Subpixel}: the LCD subpixel two-pass and the
  colored-glyph white tint are backend policy, so DrawGlyph takes a mode instead
  of the frontend driving blend funcs. Adds an explicit skew param (synthetic
  italic shears only the glyph's top edge).
- Atlas as per-page textures the backend owns: ConfigureAtlas(count,size),
  UploadAtlasRegion (glTexSubImage2D), ClearAtlasPage (drains in-flight draws via
  glFinish before reallocating — the mid-frame atlas-reset hazard).
- EndFrame() presents (SwapBuffers).

gl_renderer.go moves the existing gl.* logic from app_draw.go/atlas.go behind the
interface bit-identically (blend funcs, vertex/texcoord order, subpixel two-pass).
Vulkan/Metal stubs updated to the new method set and annotated with the
partial-redraw hazard (rotating swapchain/drawable images need a persistent
backing target — flagged so the future backend does not corrupt partial frames).

Cross-reviewed by gpt-5.6-sol; applied: ClearAtlasPage honors ConfigureAtlas's
size (not a package const), and the swapchain persistence note on the stubs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(gpu): refine Renderer interface + OpenGL adapter (Phase 0 step A)
… step B)

Wire the OpenGL glRenderer (added in Phase 0 step A) into the frontend and remove
the now-duplicated direct gl.* draw calls from the hot path. Pure code movement —
zero behavior change; the damage model, blend state, draw order, subpixel two-pass,
colored tint, skew, and pixel snapping are all preserved.

- App owns `r gpu.Renderer`; RunWithOptions builds newGLRenderer(w) after gl.Init()
  and passes it into the atlas. The atlas ctor calls r.ConfigureAtlas (sole owner).
- atlas.go no longer imports go-gl: pages are indices; drawEntry keeps the math.Round
  snapping then selects a gpu.GlyphMode and calls r.DrawGlyph; tryInsert uploads via
  r.UploadAtlasRegion (coverage LUT still applied CPU-side before upload); Reset drains
  + clears via r.ClearAtlasPage per page; close via r.Destroy. Zoom reconfigure still
  reuses the textures in place through Reset.
- draw() calls r.BeginFrame(w,h) and r.Clear(bg) only on a full redraw, so partial
  redraws never clear — the buffer-age-2 damage model is untouched. fillRect and
  drawTextDecorations became *App methods over r.FillRect; drawString/drawRune/
  drawCluster/drawRunGlyph are thin pass-throughs to the atlas. Overlays, link
  underline, and app_row route through a.fillRect. The loop presents via r.EndFrame().

The frontend hot path (app_draw, atlas, app_overlay, app_links, app_row) is now
GL-free; only gl_renderer.go and app.go's gl.Init() reference go-gl. This is the seam
a Vulkan/Metal backend plugs into.

Cross-reviewed by gpt-5.6-sol; applied: restore the original glyph-mode precedence
(subpixel wins over colored) so a colored+subpixel glyph still takes the two-pass path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gpu.Renderer contract assigns swapchain/drawable (re)creation to Resize, but
the frontend never called it — draw() went straight to BeginFrame — so a future
Vulkan/Metal backend implementing the interface could not initialize or resize its
drawable. The OpenGL adapter hid this because BeginFrame re-establishes the viewport
every frame, but the seam was not backend-sound.

draw() now calls a.r.Resize(w,h) before BeginFrame whenever the framebuffer size
changes (tracked in lastFBW/lastFBH, seeded to -1 so the first frame always drives
Resize even at a degenerate 0x0 initial size — 0 is a valid size and cannot double
as the sentinel). Steady frames skip it, so a backend's swapchain recreation never
runs per-frame. For the OpenGL backend Resize just records the size (no behavior
change); a Vulkan/Metal backend gets its init+resize signal from this one hook.

Found by the final holistic gpt-5.6-sol seam review; the -1 seed came from the Codex
pair-review; verified SOUND by Fable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(gpu): route the glfwgl render path through gpu.Renderer (Phase 0 step B)
Add the backend-neutral gpu.Renderer seam to the Unreleased notes ahead of cutting
v0.8.0-beta.1: the whole terminal render path now flows through one interface (OpenGL
adapter today, Vulkan/Metal scaffolding), with no change to on-screen output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(changelog): record the GPU renderer seam (Phase 0)
Add a build-tagged, dependency-free WebGPU Renderer stub that mirrors the Vulkan and Metal phase skeletons while documenting persistent-target, painter-order, glyph, and mid-frame atlas-reset constraints. Refresh the gpu package status now that Phase 0 is live.

Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Add Segoe UI Symbol to the Windows fallback faces so the Braille frames used by Pi render instead of falling through to the atlas question-mark replacement. Cover all ten animation frames with a visible one-cell rasterization regression.

Co-Authored-By: OpenAI <noreply@openai.com>
Preserve plain Tab as HT while emitting the standard CSI Z back-tab sequence for Shift+Tab, allowing interactive TUIs such as Pi to distinguish the shortcut. Add regression coverage for the encoded sequence.

Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Record the build-tagged WebGPU scaffold and the Shift+Tab back-tab fix in the cumulative beta changelog before publishing v0.8.0-beta.2.

Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Place DirectWrite alpha textures at the same natural-advance-centered origin as the Go raster path instead of hard-left-aligning narrow punctuation. Add a regression that keeps period ink centered within its terminal cell.

Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: OpenAI <noreply@openai.com>
cervantesh and others added 29 commits July 18, 2026 22:46
…ections

feat(frontend): build independent GLFW projections
…dow-loop

feat(frontend): run controller-owned GLFW windows
…tions

feat(actions): add typed multi-window commands
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
Co-authored-by: cervantesh <11169707+cervantesh@users.noreply.github.com>
…adata

feat(vt): retain bounded OSC 8 hyperlink metadata
…eraction

feat(frontend): gate OSC 8 link activation
…-zones

feat(vt): retain shell semantic metadata
…-actions

feat(core): expose bounded semantic history
…-action-surface

feat(actions): navigate shell prompts
…antic-zone

feat(actions): copy shell semantic zones
@cervantesh
cervantesh merged commit c7fb21c into dev Jul 20, 2026
7 checks passed
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.

1 participant