chore: synchronize dev with main - #211
Merged
Merged
Conversation
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>
…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>
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>
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
devas the integration branch without rewriting historymaindevSafety
v*)