opentui(v6): ink-budget follow-up — transparent root canvas; muted stops borrowing banner_dim
This commit is contained in:
parent
364b93a4b9
commit
4a0991c1d2
@ -12,7 +12,6 @@
|
|||||||
import { createCliRenderer, type CliRenderer, type KeyEvent, type Selection } from '@opentui/core'
|
import { createCliRenderer, type CliRenderer, type KeyEvent, type Selection } from '@opentui/core'
|
||||||
import { Deferred, Effect } from 'effect'
|
import { Deferred, Effect } from 'effect'
|
||||||
|
|
||||||
import { DEFAULT_THEME } from '../logic/theme.ts'
|
|
||||||
import { RendererError } from './errors.ts'
|
import { RendererError } from './errors.ts'
|
||||||
import { installFfiCoordSafety } from './ffiSafe.ts'
|
import { installFfiCoordSafety } from './ffiSafe.ts'
|
||||||
import { getLog } from './log.ts'
|
import { getLog } from './log.ts'
|
||||||
@ -67,10 +66,10 @@ export const acquireRenderer = Effect.fn('Renderer.acquire')(function* (options:
|
|||||||
Effect.tryPromise({
|
Effect.tryPromise({
|
||||||
try: () =>
|
try: () =>
|
||||||
createCliRenderer({
|
createCliRenderer({
|
||||||
// Root canvas (design pass): paint the dark room from the first frame
|
// Root canvas: TRANSPARENT by default — the terminal's own background
|
||||||
// — true black by default. Skin overrides land reactively via the
|
// shows through (do not paint a "default dark" canvas; glitch hated
|
||||||
// header's theme effect (view/header.tsx setBackgroundColor).
|
// it). A skin's explicit ui_bg lands reactively via the header's
|
||||||
backgroundColor: DEFAULT_THEME.color.bg,
|
// theme effect (view/header.tsx setBackgroundColor).
|
||||||
// scrollbox clips growing output → no terminal-scrollback corruption (gotcha §8 #2).
|
// scrollbox clips growing output → no terminal-scrollback corruption (gotcha §8 #2).
|
||||||
externalOutputMode: 'passthrough',
|
externalOutputMode: 'passthrough',
|
||||||
targetFps: 60,
|
targetFps: 60,
|
||||||
|
|||||||
@ -12,6 +12,12 @@
|
|||||||
*
|
*
|
||||||
* Source of truth for the contract: ui-tui/src/theme.ts (+ GatewaySkin in
|
* Source of truth for the contract: ui-tui/src/theme.ts (+ GatewaySkin in
|
||||||
* ui-tui/src/gatewayTypes.ts). Keep this port in sync if that contract changes.
|
* ui-tui/src/gatewayTypes.ts). Keep this port in sync if that contract changes.
|
||||||
|
*
|
||||||
|
* INTENTIONAL divergences from the Ink port (visual-hierarchy design pass):
|
||||||
|
* - `muted` is a true NEUTRAL grey (not a darker gold) and no longer borrows
|
||||||
|
* the skin's `banner_dim` (a banner-gold shade in the stock skin); skins
|
||||||
|
* override it via the dedicated `ui_muted` key instead.
|
||||||
|
* - a `bg` token paints the root canvas (true black/white; `ui_bg` override).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FALSE_RE, TRUE_RE } from './env.ts'
|
import { FALSE_RE, TRUE_RE } from './env.ts'
|
||||||
@ -22,9 +28,10 @@ export interface ThemeColors {
|
|||||||
border: string
|
border: string
|
||||||
text: string
|
text: string
|
||||||
muted: string
|
muted: string
|
||||||
/** Root canvas background (design pass: the view is a dark room — true black
|
/** Root canvas background. DEFAULT IS `transparent` — the terminal's own
|
||||||
* by default; skins may override via `ui_bg`). CSS names are valid OpenTUI
|
* background shows through (glitch rejected a painted "default dark" canvas;
|
||||||
* ColorInput, so the defaults use `black`/`white`, not invented hexes. */
|
* the dark room is the user's terminal, not ours). Skins may opt into a
|
||||||
|
* painted canvas via `ui_bg`. */
|
||||||
bg: string
|
bg: string
|
||||||
completionBg: string
|
completionBg: string
|
||||||
completionCurrentBg: string
|
completionCurrentBg: string
|
||||||
@ -262,7 +269,7 @@ export const DARK_THEME: Theme = {
|
|||||||
// step, CSS `gray` — no invented hexes. Grey = everything that merely
|
// step, CSS `gray` — no invented hexes. Grey = everything that merely
|
||||||
// happened; gold stays earned.
|
// happened; gold stays earned.
|
||||||
muted: '#808080',
|
muted: '#808080',
|
||||||
bg: 'black',
|
bg: 'transparent',
|
||||||
completionBg: '#1a1a2e',
|
completionBg: '#1a1a2e',
|
||||||
completionCurrentBg: '#333355',
|
completionCurrentBg: '#333355',
|
||||||
completionMetaBg: '#1a1a2e',
|
completionMetaBg: '#1a1a2e',
|
||||||
@ -308,7 +315,7 @@ export const LIGHT_THEME: Theme = {
|
|||||||
// same disease as dark: muted was `#7A5A0F` (gold-brown). True neutral —
|
// same disease as dark: muted was `#7A5A0F` (gold-brown). True neutral —
|
||||||
// the statusFg `#333333` family's lighter step, CSS `dimgray`.
|
// the statusFg `#333333` family's lighter step, CSS `dimgray`.
|
||||||
muted: '#696969',
|
muted: '#696969',
|
||||||
bg: 'white',
|
bg: 'transparent',
|
||||||
completionBg: '#F5F5F5',
|
completionBg: '#F5F5F5',
|
||||||
completionCurrentBg: mix('#F5F5F5', '#A0651C', 0.25),
|
completionCurrentBg: mix('#F5F5F5', '#A0651C', 0.25),
|
||||||
completionMetaBg: '#F5F5F5',
|
completionMetaBg: '#F5F5F5',
|
||||||
@ -445,7 +452,13 @@ export function fromSkin(
|
|||||||
|
|
||||||
const accent = c('ui_accent') ?? c('banner_accent') ?? d.color.accent
|
const accent = c('ui_accent') ?? c('banner_accent') ?? d.color.accent
|
||||||
const bannerAccent = c('banner_accent') ?? c('banner_title') ?? d.color.accent
|
const bannerAccent = c('banner_accent') ?? c('banner_title') ?? d.color.accent
|
||||||
const muted = c('banner_dim') ?? d.color.muted
|
// Design pass (Appendix C precondition): `muted` is the transcript's "merely
|
||||||
|
// happened" NEUTRAL — it must NOT borrow `banner_dim` (the stock skin's dim
|
||||||
|
// GOLD banner shade; the Ink engine still uses it for its banner-tinted dim).
|
||||||
|
// Borrowing it re-golded every dim surface in the live app and made the hero
|
||||||
|
// color the wallpaper. Skins that want a custom transcript dim ship the
|
||||||
|
// dedicated `ui_muted` key; everything else gets the theme's true neutral.
|
||||||
|
const muted = c('ui_muted') ?? d.color.muted
|
||||||
const completionBg = c('completion_menu_bg') ?? d.color.completionBg
|
const completionBg = c('completion_menu_bg') ?? d.color.completionBg
|
||||||
|
|
||||||
const completionCurrentBg =
|
const completionCurrentBg =
|
||||||
|
|||||||
@ -50,9 +50,9 @@ describe('theme — muted is a true neutral, not darker gold (design-pass precon
|
|||||||
expect(isAchromatic(DARK_THEME.color.sessionBorder)).toBe(true)
|
expect(isAchromatic(DARK_THEME.color.sessionBorder)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('bg token: true black (dark) / white (light) by CSS name', () => {
|
test('bg token: TRANSPARENT by default — the terminal owns the canvas (glitch decision)', () => {
|
||||||
expect(DARK_THEME.color.bg).toBe('black')
|
expect(DARK_THEME.color.bg).toBe('transparent')
|
||||||
expect(LIGHT_THEME.color.bg).toBe('white')
|
expect(LIGHT_THEME.color.bg).toBe('transparent')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fromSkin: skins may override bg via ui_bg; default stays the theme bg', () => {
|
test('fromSkin: skins may override bg via ui_bg; default stays the theme bg', () => {
|
||||||
@ -60,8 +60,14 @@ describe('theme — muted is a true neutral, not darker gold (design-pass precon
|
|||||||
expect(fromSkin({}, {}).color.bg).toBe(DARK_THEME.color.bg)
|
expect(fromSkin({}, {}).color.bg).toBe(DARK_THEME.color.bg)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('skin mapping intact: banner_dim still re-points muted (skins keep their dim)', () => {
|
test('muted no longer borrows banner_dim (the stock skin ships GOLD there — the live re-gold bug)', () => {
|
||||||
expect(fromSkin({ banner_dim: '#445566' }, {}).color.muted).toBe('#445566')
|
// The default Hermes skin sends banner_dim '#B8860B'; borrowing it for
|
||||||
|
// muted re-golded every dim surface in the live app. It must stay neutral.
|
||||||
|
expect(fromSkin({ banner_dim: '#B8860B' }, {}).color.muted).toBe(DARK_THEME.color.muted)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('skins keep a dedicated transcript-dim override: ui_muted', () => {
|
||||||
|
expect(fromSkin({ ui_muted: '#445566' }, {}).color.muted).toBe('#445566')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,9 @@ export function Header(props: { store: SessionStore }) {
|
|||||||
// Root canvas paint — best-effort (a styling miss must never crash chrome).
|
// Root canvas paint — best-effort (a styling miss must never crash chrome).
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const bg = theme().color.bg
|
const bg = theme().color.bg
|
||||||
|
// Default is `transparent` = leave the terminal's background alone; only a
|
||||||
|
// skin's explicit ui_bg paints the canvas.
|
||||||
|
if (bg === 'transparent') return
|
||||||
try {
|
try {
|
||||||
renderer.setBackgroundColor(bg)
|
renderer.setBackgroundColor(bg)
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user