projmux 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,11 +48,16 @@
48
48
  - `make test` also covers `projmux ai ingest claude-hook` core and extra Claude Code hook ingest for event parsing, transcript fallback, cataloged hook notification body formatting, permission summary formatting, UserPromptSubmit busy/no-queue behavior, Notification severity/text mapping, StopFailure/TeammateIdle text/severity/metadata mapping, SubagentStop quiet/no-notify diagnostics, unknown future-event quiet fallback, hook-active pane marking, topic preservation without hook candidate writes, mutable resume metadata writes for `@projmux_ai_resume_id`, source, and updated-at, and Claude transcript path preservation.
49
49
  - `make test` also covers the Globalization Phase 0 English baseline for AI desktop notification summaries/bodies and hook-ingest queue text while preserving agent names, tool names, commands, paths, and provider payload values as literals/data.
50
50
  - `make test` also covers the Globalization Phase 1 message catalog foundation: locale resolver priority and normalization, `ko-KR` to `en-US` fallback, missing fallback-key errors, `en-US` completeness for foundation keys, and plain-text versus ANSI/tmux-styled fragment API separation.
51
+ - `make test` also covers the Globalization Phase 2 formatter foundation: compact/full relative age and duration formatting, locale-specific just-now text, notification count plural/count preservation, list joins that preserve payload items, status token and target label localization, terminal cell-width measurement/truncation, and ANSI/tmux style wrappers treated as zero-width.
52
+ - `make test` also covers the Globalization Phase 3 notify runtime migration: AI notify category rendering for `en-US`/`ko-KR`, Codex/Claude hook literal preservation for agent names, tool names, commands, paths, URLs, provider messages, subagent and teammate payloads, desktop notification and in-app queue display parity, notify sidebar/statusbar locale formatter output, and raw queue storage schema preservation.
53
+ - `make test` also covers the Globalization Phase 4 Settings and popup guidance migration: `en-US`/`ko-KR` Settings root title/chips/rows/footer rendering, native picker search/empty/footer rendering without overflow, Settings row padding by terminal cell width, shell welcome guide catalog rendering, and long Korean ANSI/tmux-styled guide truncation safety while preserving key/command/path literals.
54
+ - `make test` also covers the Globalization Phase 5 locale settings surface: `PROJMUX_LOCALE` > global `[ui].locale` > `LC_ALL` > `LC_MESSAGES` > `LANG` > `en-US` priority, Settings > Appearance > Language / Locale row/detail visibility, `auto` current-locale/source display, unsupported locale fallback warnings, global config writes, and preserved command/config/env/path literals.
55
+ - `make test` also covers the Globalization Phase 6 governance guard: Go string-literal audit classification for hardcoded Korean candidates, English user-facing candidates, and ignored literal/data/debug examples; no unapproved runtime Korean literals outside catalog/formatter/test fixtures; `en-US` coverage for every embedded default catalog key; and required `ko-KR` coverage for migrated notify, Settings, picker, welcome, update, and help surfaces.
51
56
  - `make test` also covers `projmux ai integrate tmux-bell` dry-run/install/remove tmux command planning, managed `alert-bell` hook append/idempotence/removal, preservation of unmanaged bell hooks, and `projmux ai ingest bell --pane` queue push/metadata/dedupe behavior for non-AI-managed panes.
52
57
  - `make test` also covers `projmux ai ingest log` tail/path rendering and bounded JSONL log trimming for ingest diagnostics.
53
58
  - `make test` also covers welcome revisit policy: shell `skip_version` gating, `s` skip vs Enter one-run continue, Settings > About > Welcome native viewer, and attach-popup no-duplicate/no-op behavior.
54
59
  - `make test` also covers `projmux quit` action-picker rows, cancel/close no-op behavior, explicit quit of only app-owned mux runtimes marked by `@projmux_app=1` on the selected `tmux`/`psmux -L projmux` backend, missing/default runtime no-ops, dispatcher wiring, and `Settings > About > Quit projmux` routing through the same picker before any shutdown side effect.
55
- - `make test` also covers the built-in semantic palette foundation: non-empty fallback truecolor/tmux tokens, distinct action/attention/AI/progress/danger roles, native picker chip/current/titlebar render strings, statusbar git/notify/usage/settings palette regressions, attention/pane-border/popup/switch progress color guards, renderer-only lead-mode topic prefix styling, and settings/trust/destructive row color guards.
60
+ - `make test` also covers the built-in semantic palette foundation: non-empty fallback truecolor/tmux tokens, distinct action/attention/AI/progress/danger roles, native picker chip/current/titlebar render strings, statusbar git/notify/usage/settings palette regressions, attention/pane-border/popup/switch progress color guards, renderer-only lead-mode topic prefix styling, settings/trust/destructive row color guards, Theme settings Phase 0/1 resolver behavior for project/global/fallback source labels, preset fill, explicit token override, invalid-layer warnings, and truecolor-to-tmux mapping, Phase 2 fallback render parity and project/global color isolation for native picker and tmux status/window background adapters, Phase 3 desired font config save/resolve plus unsupported `not applied` status without breaking no-adapter fallback rendering, and Phase 4 Settings theme editing boundaries for project/global resets, project inherit-vs-override labels, effective source labels, and project config theme values feeding the native project popup render path.
56
61
  - Current focused unit coverage also includes strict notify SOT behavior
57
62
  (TTL does not remove rows, focus success and target-gone clicks ack,
58
63
  reconcile reports stale rows), `notify list --live` queue/live explanations, notify sidebar
@@ -158,6 +158,96 @@ chord and the replacement before binding the merged action. Popup and floating
158
158
  UI actions still route through `tmux popup-toggle`, so pressing the same
159
159
  configured key opens and closes the popup.
160
160
 
161
+ ## Theme Resolver Foundation
162
+
163
+ Theme settings are resolved against the same project/global axes as
164
+ declarative hooks and project recipe fields:
165
+
166
+ ```text
167
+ <project>/.projmux/config.toml
168
+ ~/.config/projmux/config.toml
169
+ ```
170
+
171
+ Settings can edit the global `[theme]` in `~/.config/projmux/config.toml` and
172
+ the current project override in `<project>/.projmux/config.toml`. The Effective
173
+ theme view shows the final project > global > built-in fallback value for each
174
+ field with source labels: `project`, `global`, or `fallback`.
175
+
176
+ Renderer adapters can apply an already resolved `EffectiveTheme` to native
177
+ picker frame background/foreground SGR and tmux status/window `colourN`
178
+ background tokens. Settings and native project picker surfaces load global and
179
+ project `[theme]` values through the shared effective-theme source. Fallback
180
+ renderer output intentionally keeps the existing palette constants byte for
181
+ byte.
182
+
183
+ Resolver schema shape:
184
+
185
+ ```toml
186
+ [theme]
187
+ preset = "projmux-dark"
188
+ background = "#182226"
189
+ surface = "#182226"
190
+ surface_active = "#2c383d"
191
+ foreground = "#d8e0e4"
192
+ muted = "#75848c"
193
+ accent = "#7ac7ad"
194
+ critical = "#ff6b6b"
195
+ warning = "#ffcc66"
196
+ font_family = "Cascadia Mono"
197
+ font_size = 12
198
+ ```
199
+
200
+ Supported presets are `projmux-dark`, `midnight`, `forest`, `rose`, and
201
+ `high-contrast`. A preset fills missing color tokens in its own layer, and
202
+ explicit color tokens override preset values. Missing or `inherit` values fall
203
+ through to the next layer.
204
+
205
+ Unknown presets and invalid color/font values invalidate only their own theme
206
+ layer and produce resolver warnings; the next source still resolves normally.
207
+ Colors are `#RRGGBB`. Settings edits colors through a preset selector, swatch
208
+ rows, and a hex input page. Truecolor renderers use exact RGB SGR tokens, and
209
+ tmux surfaces use the stored or nearest xterm 256-color `colourN` mapping. Font
210
+ values are desired terminal profile hints, not universal tmux or ANSI renderer
211
+ tokens. Without a supported terminal font adapter, Settings reports the
212
+ effective desired font as `not applied`; projmux does not create or modify
213
+ terminal profiles in this phase.
214
+
215
+ ## UI Locale
216
+
217
+ The UI locale can be pinned globally or left on automatic detection.
218
+
219
+ Preferred interactive path:
220
+
221
+ - `Settings > Appearance > Language / Locale`
222
+
223
+ Global config path:
224
+
225
+ ```text
226
+ ~/.config/projmux/config.toml
227
+ ```
228
+
229
+ Schema:
230
+
231
+ ```toml
232
+ [ui]
233
+ locale = "auto" # auto | en-US | ko-KR
234
+ ```
235
+
236
+ Resolution priority is:
237
+
238
+ 1. `PROJMUX_LOCALE`
239
+ 2. global/user `[ui] locale`
240
+ 3. auto-detected environment: `LC_ALL`, then `LC_MESSAGES`, then `LANG`
241
+ 4. built-in fallback `en-US`
242
+
243
+ `auto` means detect from the environment. In Settings, the `auto` detail shows
244
+ the currently detected locale and source. Supported UI locales are `en-US` and
245
+ `ko-KR`; unsupported tags fall back to `en-US` and the Settings detail shows a
246
+ warning with the unsupported value and source.
247
+
248
+ Project-local locale override is not part of the runtime policy. Locale is a
249
+ user/global preference in this release.
250
+
161
251
  ## Environment Variables
162
252
 
163
253
  | Variable | Purpose |
@@ -165,6 +255,7 @@ configured key opens and closes the popup.
165
255
  | `PROJMUX_PROJDIR` | Explicit primary project root. Accepts an OS-native PATH-style multi-value: the first non-empty entry is the primary root and later entries are prepended to managed-root discovery. The primary value is memoized to `~/.config/projmux/projdir`. |
166
256
  | `PROJMUX_MANAGED_ROOTS` | Search-root override. Uses the OS-native path-list separator and takes priority over the saved workdirs file and default weak probes. |
167
257
  | `TMUX_SESSIONIZER_ROOTS` | Legacy alias still honored at runtime for managed roots. |
258
+ | `PROJMUX_LOCALE` | UI locale override. `auto` resumes detection; `en-US` and `ko-KR` pin supported locales. Unsupported tags fall back to `en-US` and surface a Settings warning. |
168
259
  | `PROJMUX_NOTIFY_HOOK` | External executable that receives AI desktop notifications instead of the built-in Linux/WSL sender. Separate from declarative `[hooks.send-noti]`. |
169
260
  | `PROJMUX_NOTIFY_HOOK_DEPTH` | Internal recursion guard for `send-noti` hooks. Depth `>= 1` suppresses nested hook dispatch while still allowing the queue write itself. |
170
261
  | `PROJMUX_NOTIFY_EXPIRE_MS` | AI desktop notification expiration in milliseconds. Defaults to `5000`; unset, zero, negative, and non-numeric values fall back to the default. |
@@ -1,9 +1,9 @@
1
1
  # Globalization Contract
2
2
 
3
3
  This document is the inventory and contract for globalization. Phase 0 defined
4
- the user-facing text boundary and literal preservation rules. Phase 1 adds the
5
- message catalog foundation, locale resolver, fallback policy, and contribution
6
- rules for future runtime surface migrations.
4
+ the user-facing text boundary and literal preservation rules. Later phases add
5
+ the message catalog, locale formatters, runtime surface migration, and the
6
+ Phase 5 user locale override surface.
7
7
 
8
8
  ## Phase 0 Scope
9
9
 
@@ -210,13 +210,16 @@ Catalog policy:
210
210
 
211
211
  Locale resolution:
212
212
 
213
- - Precedence is explicit override/API, then `LC_ALL`, then `LC_MESSAGES`, then
214
- `LANG`, then `en-US`.
213
+ - Precedence is explicit override/API, then `PROJMUX_LOCALE`, then global
214
+ `[ui] locale`, then `LC_ALL`, then `LC_MESSAGES`, then `LANG`, then `en-US`.
215
215
  - Common POSIX forms normalize into catalog tags: `ko`, `ko_KR.UTF-8`, and
216
216
  `ko-KR` become `ko-KR`; `en` and `en_US` become `en-US`.
217
217
  - `C` and `POSIX` locales are skipped as non-user-language values.
218
- - Phase 1 does not add Settings language UI, persisted config/env locale
219
- schema, or runtime surface migration.
218
+ - Supported UI locales are currently `en-US` and `ko-KR`. Unsupported locale
219
+ tags fall back to `en-US` and keep their source attached so Settings can show
220
+ a warning.
221
+ - `auto` is a setting value, not a catalog locale. It means "use the
222
+ auto-detected environment rung".
220
223
 
221
224
  API convention:
222
225
 
@@ -242,3 +245,205 @@ Contribution convention:
242
245
  - Runtime migrations should be narrow by surface. Move a string family behind
243
246
  the catalog only when its tests can show literal preservation and fallback
244
247
  behavior for that surface.
248
+
249
+ ## Phase 2 Formatter Foundation
250
+
251
+ Package:
252
+
253
+ - `internal/i18n`
254
+
255
+ Formatter policy:
256
+
257
+ - Formatter functions localize grammar, units, and labels. They do not translate
258
+ caller-owned payload values such as product names, commands, paths, provider
259
+ text, tmux targets, or enum/source data inserted as arguments.
260
+ - Formatter functions accept a `FormatVariant`. `i18n.FormatFull` is for
261
+ prose-friendly labels and `i18n.FormatCompact` is for dense terminal surfaces.
262
+ - Unsupported or empty locales fall back to `en-US`. Korean locale forms
263
+ normalized by `ResolveLocale` use the Korean formatter rules.
264
+ - Relative age has a pinned just-now window of less than five seconds:
265
+ `just now` for `en-US`, `방금 전` for `ko-KR`.
266
+
267
+ Formatter API:
268
+
269
+ - `i18n.FormatRelativeAge(age, locale, variant)` renders elapsed age, for
270
+ example `3m ago` in compact `en-US` and `36초 전` in compact `ko-KR`.
271
+ - `i18n.FormatDuration(duration, locale, variant)` renders the largest whole
272
+ unit in seconds, minutes, hours, or days.
273
+ - `i18n.FormatCount(count, subject, locale, variant)` applies locale-specific
274
+ count grammar for owned subjects such as `i18n.CountNotifications` while
275
+ preserving the numeric value.
276
+ - `i18n.FormatList(items, locale, variant)` joins caller-owned item strings
277
+ using locale-specific separators without translating item payloads.
278
+ - `i18n.FormatStatusToken(token, locale, variant)` renders shared terminal
279
+ status labels for known `i18n.StatusToken` values.
280
+ - `i18n.FormatTargetLabel(kind, number, locale, variant)` localizes the target
281
+ label, such as window or pane, while preserving the target number.
282
+
283
+ Width-safe rendering:
284
+
285
+ - Use `i18n.TerminalCellWidth(value)` when terminal layout depends on visible
286
+ width. It measures terminal cells, not bytes or runes.
287
+ - Use `i18n.TruncateTerminalCells(value, maxCells)` before placing
288
+ locale-specific output into fixed-width terminal columns.
289
+ - ANSI escape sequences and tmux style wrappers such as `#[fg=red]` are
290
+ zero-width for measurement and truncation. Truncation preserves those wrappers
291
+ in the returned string while clipping only visible content.
292
+ - Width clipping is a rendering concern. Keep translated format strings and
293
+ caller-owned payload arguments separate until the final render step.
294
+
295
+ ## Phase 3 Notify Runtime Migration
296
+
297
+ Runtime surfaces migrated:
298
+
299
+ - AI desktop notification summaries for Codex and Claude hook payloads.
300
+ - In-app notify queue table/sidebar/statusbar display text for AI entries.
301
+ - Notify live explanation text for `projmux notify list --live`.
302
+ - Sidebar/table/statusbar age formatting and sidebar stale/gone/target labels.
303
+
304
+ Storage and dispatch policy:
305
+
306
+ - Queue storage remains schema-compatible. Stored `notify.Notification.Text`,
307
+ metadata keys, severity/source enum values, targets, and timestamps are not
308
+ localized before persistence.
309
+ - OS notification click/focus routing is unchanged. Locale formatting only
310
+ changes the summary/body strings sent to the configured notifier.
311
+ - JSON queue payloads keep raw queue entries. Localized live-row explanation
312
+ and row display text may appear in `notify list --live` row fields because
313
+ those fields are render/report output, not stored queue schema.
314
+
315
+ Literal preservation and parity rules:
316
+
317
+ - Translate only catalog-owned category labels such as `Response complete`,
318
+ `Approval required`, `Input required`, `Error`, `Subagent stopped`, and
319
+ `Teammate waiting`.
320
+ - Preserve provider-owned payloads verbatim: `Codex`, `Claude`, tool names,
321
+ commands, paths, URLs, query strings, transcript excerpts, teammate IDs, and
322
+ subagent IDs.
323
+ - Desktop notification summaries and in-app queue display text must use the
324
+ same rendered category labels for the same locale while preserving the same
325
+ literal payload body.
326
+ - The response-complete fallback body `Ready` is treated as provider state and
327
+ suppressed in display detail; it is not translated or persisted differently.
328
+
329
+ ## Phase 4 Settings And Popup Guidance
330
+
331
+ Runtime surfaces migrated:
332
+
333
+ - Settings root title, scope chips, root row labels/descriptions, common row
334
+ labels/previews/disabled reasons, and shared Settings footer guidance.
335
+ - Native picker search label, empty-list row, and line-mode close prompt.
336
+ - Shell welcome/update guide text and Settings > About > Welcome viewer body.
337
+
338
+ Literal preservation:
339
+
340
+ - Key names such as `Enter`, `Esc`, `Ctrl-b d`, and `s` remain literal inside
341
+ localized guide sentences.
342
+ - Commands, env/config names, and paths such as `projmux shell`,
343
+ `tmux -L projmux kill-server`, `PROJMUX_PROJDIR`, and
344
+ `~/.config/projmux/projdir` remain literal payload text.
345
+ - Project names, paths, provider payloads, enum values, and hook/action IDs are
346
+ not translated.
347
+
348
+ Width policy:
349
+
350
+ - Settings row padding now uses terminal cell width instead of byte length so
351
+ Korean labels align with the existing ANSI row chrome.
352
+ - Native picker prompt, empty row, and footer render through existing frame
353
+ truncation/padding helpers; focused tests cover `en-US`/`ko-KR` output and
354
+ long Korean styled guidance.
355
+
356
+ ## Phase 5 Locale Settings And Docs
357
+
358
+ User override surface:
359
+
360
+ - Environment override: `PROJMUX_LOCALE=auto|en-US|ko-KR`.
361
+ - Global config override: `~/.config/projmux/config.toml`:
362
+
363
+ ```toml
364
+ [ui]
365
+ locale = "auto" # auto | en-US | ko-KR
366
+ ```
367
+
368
+ - Settings surface: `Settings > Appearance > Language / Locale`.
369
+
370
+ Resolution policy:
371
+
372
+ 1. API/explicit override used by tests and internal callers.
373
+ 2. `PROJMUX_LOCALE` when set to a non-empty value other than `auto`.
374
+ 3. Global/user `[ui] locale` when set to a non-empty value other than `auto`.
375
+ 4. Auto-detected environment, in order: `LC_ALL`, `LC_MESSAGES`, `LANG`.
376
+ 5. Built-in fallback `en-US`.
377
+
378
+ `auto` in either `PROJMUX_LOCALE` or `[ui] locale` does not pin the UI to a
379
+ literal `auto` locale. It re-enters the auto-detection path and Settings shows
380
+ the currently detected locale and source, such as `ko-KR from LC_MESSAGES env`.
381
+
382
+ Fallback and warning policy:
383
+
384
+ - `en-US` and `ko-KR` are the only supported UI locales in Phase 5.
385
+ - Unsupported locale tags such as `ja-JP` or `fr-FR` fall back to `en-US`.
386
+ - Settings displays the unsupported tag, its source (`PROJMUX_LOCALE`,
387
+ `~/.config/projmux/config.toml`, `LC_ALL`, `LC_MESSAGES`, or `LANG`), and
388
+ the effective `en-US` fallback.
389
+ - Project-local locale override is intentionally out of scope. Any parser
390
+ support for `[ui] locale` exists only because global and project config share
391
+ the same TOML implementation; runtime locale resolution consumes only the
392
+ global/user config path.
393
+
394
+ Literal preservation remains unchanged:
395
+
396
+ - Config keys and env vars such as `[ui].locale`, `PROJMUX_LOCALE`,
397
+ `LC_MESSAGES`, and `~/.config/projmux/config.toml` remain literal.
398
+ - Locale enum values such as `auto`, `en-US`, and `ko-KR` remain literal.
399
+ - Commands, paths, provider payloads, tmux format strings, and key names remain
400
+ caller-owned payload text and are not translated.
401
+
402
+ ## Phase 6 Governance
403
+
404
+ Phase 6 prevents new user-facing strings from bypassing the catalog while
405
+ keeping the existing literal/data/debug boundary explicit.
406
+
407
+ Governance checks:
408
+
409
+ - `internal/i18n` owns the Go string-literal audit helper.
410
+ - `go test ./internal/i18n` includes synthetic audit coverage for Korean
411
+ candidates, English user-facing candidates, and ignored literal/data/debug
412
+ examples.
413
+ - The current repo guard scans runtime Go files for new Korean string literals
414
+ outside the catalog, formatter locale fragments, tests, `testdata`, and
415
+ comments.
416
+ - The catalog completeness tests require `en-US` fallback coverage for every
417
+ embedded default catalog key and required `ko-KR` coverage for migrated
418
+ `notify.ai.`, `notify.live.`, `settings.`, `picker.`, `welcome.`, `update.`,
419
+ and `help.` surfaces.
420
+
421
+ Contributor rule:
422
+
423
+ - New normal UX copy must be added as a stable catalog key with an `en-US`
424
+ fallback entry and focused test coverage.
425
+ - If the string is intentionally not translated, classify it in review notes as
426
+ `literal`, `data`, or `debug-only` and preserve it verbatim.
427
+ - Korean product UI strings must not be hardcoded in runtime Go. Add or extend
428
+ the catalog entry instead.
429
+ - Do not translate provider payloads, commands, config keys, paths, env vars,
430
+ locale enum values, product names, or tmux/ANSI syntax.
431
+
432
+ Debug/log/internal error classification:
433
+
434
+ - Catalog text when it is normal UX, shown in a picker/sidebar/statusbar,
435
+ desktop notification, user-actionable CLI output, or other expected product
436
+ surface.
437
+ - Do not catalog debug logs, trace strings, diagnostics, or wrapped internal Go
438
+ errors unless that exact text is promoted into normal UX.
439
+ - If a diagnostic is surfaced as normal UX, split it: catalog the user-facing
440
+ explanation or action hint, and keep raw error details, paths, commands, and
441
+ provider payload as preserved data.
442
+
443
+ Audit operation:
444
+
445
+ - Run `GOCACHE=/tmp/projmux-go-cache go test ./internal/i18n` after changing
446
+ i18n helpers, catalog data, migrated UI strings, or locale policy.
447
+ - For broader validation, continue to run the standard repo gates in order:
448
+ `make fmt`, `make fix`, `make test`, `make test-integration`, and
449
+ `make test-e2e` where applicable.
@@ -69,6 +69,11 @@ Use this template:
69
69
  - [ ] make fmt-check
70
70
  - [ ] make test
71
71
  - [ ] manual verification step (if relevant)
72
+
73
+ ## Globalization
74
+ - [ ] No user-facing string changes.
75
+ - [ ] User-facing strings are behind `internal/i18n` catalog keys with tests.
76
+ - [ ] Non-translated strings are classified as literal/data/debug-only.
72
77
  ```
73
78
 
74
79
  Notes:
@@ -76,6 +81,11 @@ Notes:
76
81
  - **Why** matters more than **what**. Diff already shows the what.
77
82
  - Reference issues with `Closes #<n>` so they auto-close on merge.
78
83
  - Mention follow-ups explicitly when scope was deliberately deferred.
84
+ - For any new or changed user-facing text, check exactly one Globalization
85
+ item. Normal UX copy needs a catalog key and test coverage. Commands, paths,
86
+ config keys, env vars, provider payloads, locale enum values, product names,
87
+ debug logs, and internal diagnostics may stay out of the catalog only when
88
+ explicitly classified in the PR body.
79
89
 
80
90
  ## Branch protection in effect
81
91
 
@@ -71,6 +71,17 @@ view-first layout:
71
71
  and `Notify icon` details. Each detail shows the current mode plus
72
72
  immediately selectable off/symbol/emoji preview rows. There is no separate
73
73
  `Change` page for icon decoration.
74
+ - `Settings > Appearance` also shows a read-only `Theme font` status row.
75
+ `font_family` and `font_size` are desired values from the effective
76
+ project/global theme, and unsupported terminal paths must say `not applied`
77
+ instead of implying tmux changed the font.
78
+ - `Settings > Appearance > Language / Locale` is the global/user language
79
+ detail. The root row shows the saved `[ui].locale` value and the currently
80
+ effective locale. The detail shows `Current`, `[ui].locale`, optional
81
+ `PROJMUX_LOCALE` env override, and direct choices for `auto`, `en-US`, and
82
+ `ko-KR`. When `auto` is active it must show the detected source (`LC_ALL`,
83
+ `LC_MESSAGES`, `LANG`, or fallback). Unsupported locale tags must remain
84
+ visible as warnings and fall back to `en-US`.
74
85
 
75
86
  Hooks remain the reference pattern for this IA:
76
87
 
package/docs/statusbar.md CHANGED
@@ -86,6 +86,11 @@ The notify segment renders the newest queued item as a single notification
86
86
  block: project, state (`NEED`/`INFO`/`WARN`/`CRIT`), optional agent, text,
87
87
  age, and `+N` for older pending entries. Window/pane ids are not shown in the
88
88
  compact status segment.
89
+ The compact age text is locale-formatted through `internal/i18n` (`2m ago` in
90
+ `en-US`, `36초 전` in `ko-KR`). AI notify body text uses catalog-owned category
91
+ labels while preserving agent names, commands, paths, URLs, and provider
92
+ payload excerpts. The `+N` older-entry count remains a numeric compact badge so
93
+ it does not expand the status segment.
89
94
  When the notify block is wider than its cell budget, clipping shrinks the body
90
95
  text first and appends an ellipsis while preserving project, state, agent, age,
91
96
  and count metadata. If the segment is still too wide, the age is dropped next
@@ -186,6 +191,9 @@ updates the matching live tmux option
186
191
  (`@projmux_statusbar_decoration_cwd`, `_git`, or `_notify`) when run inside
187
192
  tmux. The legacy `~/.config/projmux/statusbar-decoration` and
188
193
  `@projmux_statusbar_decoration` remain fallback defaults for older configs.
194
+ Appearance also shows the effective desired theme font. This is a status row,
195
+ not a font editor: tmux status strings and ANSI output cannot force terminal
196
+ font family or size, so unsupported environments report `not applied`.
189
197
 
190
198
  To add a new clickable segment:
191
199
 
@@ -6,31 +6,70 @@ truth in code is `internal/theme/palette.go`.
6
6
 
7
7
  ## Scope
8
8
 
9
- The fallback palette is a semantic token layer, not a user configuration
10
- schema. It gives current renderers shared names for the colors stabilized by
11
- the Visual palette baseline work:
9
+ The fallback palette is a semantic token layer. Theme settings resolve project
10
+ and global config into the resolver-facing token inventory below, then fall
11
+ back to the built-in values from `internal/theme/palette.go`.
12
12
 
13
13
  - Native picker truecolor SGR tokens.
14
14
  - Native sidebar and chip-strip 256-color SGR tokens.
15
15
  - Tmux statusbar and generated-config color tokens.
16
16
  - Settings/action/state/trust/attention helper tokens.
17
17
 
18
- Future Theme settings should resolve project/global values into this token
19
- shape, then keep the built-in values as the final fallback. This phase does not
20
- add `config.toml` fields, a resolver, a Settings editor, presets, import, or
21
- export.
18
+ Renderer adapters apply resolver-backed background/foreground colors to native
19
+ picker frame chrome and to tmux status/window background tokens when an
20
+ `EffectiveTheme` is supplied by the caller. Fallback-sourced fields still
21
+ render through the historical constants so built-in default output remains
22
+ byte-identical. Settings and native project picker surfaces load `[theme]`
23
+ values from global and project config through the shared effective-theme source.
24
+ Theme marketplace/import/export and Visual palette reselection remain out of
25
+ scope.
26
+
27
+ ## Resolver Token Inventory
28
+
29
+ The public resolver inventory is intentionally smaller than the current
30
+ renderer literal inventory. Surface-specific renderers map their detailed roles
31
+ onto these stable names:
32
+
33
+ | Token | Meaning | Shared surfaces |
34
+ | --- | --- | --- |
35
+ | `background` | base popup/sidebar/status surface background | native picker, frame titlebar, notify sidebar, settings popup, statusbar |
36
+ | `surface` | raised or inactive chrome surface | frame titlebar, chips, switch cards, settings popup |
37
+ | `surface_active` | selected/current row or active chip surface | native picker current row, frame chips, statusbar active window |
38
+ | `foreground` | primary readable text | native picker, titlebar, statusbar, notify sidebar, settings popup |
39
+ | `muted` | secondary text, divider, disabled or stale details | picker metadata, titlebar rule, notify age/stale, settings descriptions |
40
+ | `accent` | pointer, primary action, highlight, active affordance | native picker pointer/highlight, settings actions, chips |
41
+ | `critical` | destructive/error/critical state | settings remove/quit, notify critical badge, statusbar critical usage |
42
+ | `warning` | progress, pending, warning, busy state | AI busy/thinking indicators, notify pending title, usage warning |
43
+
44
+ Renderer-only role names such as `accent.ai`, `state.progress`, `git.branch`,
45
+ and trust colors remain in `internal/theme/palette.go` until Phase 2+ maps each
46
+ surface to the resolver tokens. The key product contract for this phase is that
47
+ native picker, frame titlebar, chips, statusbar, notify sidebar, and settings
48
+ popup all consume a shared effective token set instead of independently
49
+ choosing colors.
50
+
51
+ Font is not part of this universal token inventory. `font_family` and
52
+ `font_size` are resolved as terminal capability/profile hints: projmux can
53
+ store and display the desired value, but tmux/ANSI rendering cannot force a
54
+ font family or size across terminal emulators. In environments without a
55
+ supported terminal font adapter, projmux reports the desired font as
56
+ `not applied` instead of treating storage as a successful font change.
22
57
 
23
58
  ## Mapping Policy
24
59
 
25
60
  Native picker rows can emit truecolor SGR, while tmux statusbar/config strings
26
- must use tmux color specs. The fallback therefore stores both forms when a
27
- role crosses surfaces.
61
+ must use tmux color specs. The resolver therefore carries both forms for each
62
+ color token.
28
63
 
29
64
  Rules:
30
65
 
31
- - Truecolor tokens keep exact SGR strings for native picker chrome and
32
- Settings rows.
66
+ - Truecolor tokens keep exact `#RRGGBB` values and can be converted to
67
+ foreground/background SGR fragments such as `38;2;R;G;B` or `48;2;R;G;B`.
33
68
  - Tmux tokens keep `colourN` strings where tmux owns rendering.
69
+ - The built-in `projmux-dark` fallback uses the established ANSI and tmux
70
+ tokens from `internal/theme/palette.go` to preserve current output.
71
+ - Explicit `#RRGGBB` overrides keep exact truecolor and derive the closest
72
+ xterm 256-color `colourN` token for tmux surfaces.
34
73
  - Native chip/sidebar badge tokens use 256-color SGR when they intentionally
35
74
  mirror tmux colors.
36
75
  - Output compatibility wins inside this baseline. For example, the kube
@@ -39,6 +78,35 @@ Rules:
39
78
  - Renderers should reference semantic names instead of spelling color literals
40
79
  directly. Test fixtures may still pin rendered escape strings.
41
80
 
81
+ ## Resolver Contract
82
+
83
+ Theme resolution is field-by-field after validating each layer:
84
+
85
+ 1. Project `.projmux/config.toml`
86
+ 2. Global `~/.config/projmux/config.toml`
87
+ 3. Built-in fallback preset `projmux-dark`
88
+
89
+ Rules:
90
+
91
+ - Project values override global values for the same field.
92
+ - Missing or `inherit` project values fall back to global values.
93
+ - Missing global values fall back to built-in values.
94
+ - A preset fills missing color tokens in its own layer.
95
+ - Explicit color tokens in the same layer override preset colors.
96
+ - An unknown preset invalidates only that layer and emits a warning.
97
+ - An invalid color, `font_family`, or `font_size` invalidates only that layer
98
+ and emits a warning.
99
+ - Every effective field reports `project`, `global`, or `fallback` as its
100
+ source label.
101
+
102
+ Built-in preset config values are:
103
+
104
+ - `projmux-dark`
105
+ - `midnight`
106
+ - `forest`
107
+ - `rose`
108
+ - `high-contrast`
109
+
42
110
  ## Fallback Inventory
43
111
 
44
112
  Chrome and text:
@@ -90,6 +158,7 @@ After the Phase 3 token pass, raw color values intentionally remain in:
90
158
  The converted implementation paths include:
91
159
 
92
160
  - `internal/ui/projmuxpicker/ansi.go`
161
+ - `internal/ui/projmuxpicker/frame.go`
93
162
  - `internal/app/tmux.go`
94
163
  - `internal/app/status.go`
95
164
  - `internal/app/statusbar.go`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -24,10 +24,10 @@
24
24
  "LICENSE"
25
25
  ],
26
26
  "optionalDependencies": {
27
- "@projmux/darwin-arm64": "0.6.4",
28
- "@projmux/darwin-x64": "0.6.4",
29
- "@projmux/linux-arm64": "0.6.4",
30
- "@projmux/linux-x64": "0.6.4"
27
+ "@projmux/darwin-arm64": "0.6.5",
28
+ "@projmux/darwin-x64": "0.6.5",
29
+ "@projmux/linux-arm64": "0.6.5",
30
+ "@projmux/linux-x64": "0.6.5"
31
31
  },
32
32
  "scripts": {
33
33
  "package:npm": "scripts/package-npm.sh",