projmux 0.6.4 → 0.6.6

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.
@@ -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.
package/docs/hooks.md CHANGED
@@ -206,7 +206,11 @@ desktop sender and receives positional arguments
206
206
  `summary body urgency app-name tag group icon-path`. That `urgency` value is
207
207
  the OS notification urgency, not the notify-queue severity. AI approval,
208
208
  input, selection, and confirmation rows can stay critical in the queue and UI
209
- while the desktop notification hook receives `normal`.
209
+ while the desktop notification hook receives `normal`. Live AI status badges
210
+ are a third surface: permission/input-required panes use the action-required
211
+ amber-orange status role, response-complete panes use success green, and
212
+ in-progress panes use progress yellow. They do not inherit the critical queue
213
+ severity, and permission/input status badges do not use red.
210
214
 
211
215
  ## Codex Hooks Engine
212
216
 
@@ -12,8 +12,7 @@ supported fallback guidance.
12
12
  The recommended path when a key does not fire:
13
13
 
14
14
  1. Try the key inside `projmux shell`.
15
- 2. Open Settings > Keybindings > Diagnostic, or run `projmux setup` outside
16
- tmux, to see which bytes reach the process.
15
+ 2. Run `projmux setup` outside tmux to see which bytes reach the process.
17
16
  3. For supported terminals, preview `projmux init [terminal]`; add `--apply`
18
17
  only after reviewing the merge.
19
18
  4. For unsupported terminals, configure plain Meta bytes or add a tmux alias in
@@ -47,6 +46,12 @@ keymap actions, pane switching, window switching, and rename actions remain
47
46
  visible. Transport-dependent rows show the default transport key separately
48
47
  from editable plain aliases.
49
48
 
49
+ The Settings flow is intentionally simple: the root is one action list with
50
+ current key summaries, and each action detail shows the action, current
51
+ keybinding/aliases, `Add alias`, and reset. Diagnostic/probe/init workflows are
52
+ not first-class Settings tabs; use `projmux setup` and `projmux init` from the
53
+ terminal when key delivery needs remediation.
54
+
50
55
  Optional direct aliases can be added for actions such as:
51
56
 
52
57
  | Canonical action | Meaning |
@@ -67,19 +72,20 @@ without storing or replacing the transport default. Rename actions no longer
67
72
  have a built-in terminal fallback; use tmux's prefix rename flow or configure
68
73
  an explicit safe alias where the action is editable.
69
74
 
70
- ## Roadmap Requirements
75
+ ## Product Requirements
71
76
 
72
- Follow-up Phase 2 keeps Settings > Keybindings as a discovery surface. It must
73
- continue to expose launch toggles, sidebar keymap actions, picker-local actions,
74
- pane switching, window switching, and rename actions. Transport-dependent rows
75
- should explain the default transport key and offer only additive safe plain
76
- aliases; diagnostic-only rows should explain why they are not editable.
77
+ Settings > Keybindings stays a discovery surface. It must continue to expose
78
+ launch toggles, sidebar keymap actions, picker-local actions, pane switching,
79
+ window switching, and rename actions. The primary Settings flow is not the
80
+ terminal remediation surface: replace-primary, disable-default, typed fallback,
81
+ terminal mapping preview/apply, and init execution rows stay out of the action
82
+ detail.
77
83
 
78
- Follow-up Phase 3 removes the `UserN` / `CSI-u` route from the product model.
79
- Windows Terminal and Ghostty-centered replacements should use plain
80
- Meta/control chords or xterm modifier sequences where possible. If a key cannot
81
- be represented that way, leave it as a non-editable unsupported or diagnostic
82
- row instead of preserving a User-key or CSI-u fallback.
84
+ The product model does not support `UserN` or `CSI-u` as fallback guidance.
85
+ Windows Terminal and Ghostty adapters use built-in plain Meta/control bytes or
86
+ xterm modifier sequences where possible. If a key cannot be represented that
87
+ way, leave it as a non-editable unsupported or diagnostic row instead of
88
+ preserving a User-key or CSI-u fallback.
83
89
 
84
90
  ## Picker Actions
85
91
 
@@ -131,8 +137,8 @@ names: `ProjectSidebarToggle`, `NotifySidebarToggle`, `SessionPopupToggle`,
131
137
  ## Diagnose: `projmux setup`
132
138
 
133
139
  Run `projmux setup` outside tmux to find out which projmux keys reach the raw
134
- terminal. The same diagnostic is available in-app at Settings > Keybindings >
135
- Diagnostic.
140
+ terminal. Settings > Keybindings remains the action/alias editor; setup is the
141
+ terminal delivery diagnostic.
136
142
 
137
143
  | Status | Meaning |
138
144
  | --- | --- |
@@ -46,8 +46,9 @@ native picker engine and is not a public dependency-policy change.
46
46
  | fzf navigation keys | interactive selection in searchable lists | Covered | native maps modified-key `Ctrl-J` plus `Ctrl-N` to down and `Ctrl-P`/`Ctrl-K` to up when not claimed by a custom action; up/down-family movement is safe on empty lists; raw LF remains Enter for PTY compatibility; `TestNativeInteractiveSupportsFZFNavigationKeys`; `TestNativeInteractiveNavigationKeysAreNoopForEmptyList` |
47
47
  | alternate-screen lifecycle | fzf fullscreen picker screen restore | Covered | native frame updates and screen exit return to column 0 before terminal control sequences, screen exit resets styles plus clears the alternate buffer from the home cursor before restore, and real TTY restores get a short settle window before caller handoff; `nativeScreenEnter`; `TestNativeInteractiveUsesAlternateScreen`; `TestRenderFullFrameUpdateAlwaysHomesAndWritesFrame` |
48
48
  | frame content width | fzf border inner width | Covered | `ContentLayout` uses the frame inner width so separators and rows reach the right border; `TestRendererContentLayoutUsesFrameInnerWidth` |
49
+ | picker-owned app background | native picker frame interior | Covered for renderer-owned cells | `ThemeFromEffective` applies built-in fallback and explicit effective background/foreground SGR to the native frame, and frame rows resume the app style after embedded resets so content padding, empty no-footer rows, footer rows, scrollbars, and preview gaps do not leak terminal default background; `TestThemeFromEffectiveFallbackPaintsFrameBackground`; `TestRendererFrameBackgroundResumesAfterContentResetBeforePadding`; `TestNativeInteractiveNoFooterBlankRowsUseThemeBackground`; `TestNativeInteractiveSplitPreviewGapsUseThemeBackground`; `TestNativeInteractiveSettingsAIBadgeStyleLongPreviewClampsFrameRows` |
49
50
  | tmux popup frame interaction | native picker popups launched through `popup-toggle` | Covered for native backend popups | `popup-toggle` passes tmux `display-popup -B` when `PROJMUX_PICKER_BACKEND=native`, so the native picker owns the visible frame instead of double-drawing with the tmux popup border; native Alt-1 uses a compact native-only minimum while fzf keeps the previous project sidebar minimum; Alt-1/Alt-2 sidebar heights reserve two bottom statusbar rows; Alt-2 notify sidebar keeps the fzf-like `24%` / min `64` baseline; `TestAppRunTmuxPopupToggleUsesBorderlessPopupForNativeBackend`; `TestSessionizerSidebarWidthKeepsFZFMinimum`; `TestSidebarPopupHeightLeavesStatusbarRows`; `TestNotifySidebarWidthMatchesFZFBaseline`; `TestAppRunTmuxPopupToggleKeepsNotifySidebarFZFSizingForNative` |
50
- | optional native titlebar | native picker popup frame | Covered for empty-title compatibility and opt-in titles | `picker.Options.Title`; `RenderFrameWithTitle`; `ContentLayoutWithTitle`; empty titles keep the prior frame unchanged, non-empty titles render in a picker-owned section below the top border with neutral titlebar background, rule fill, and a divider above search/content, and the native Alt-1 project sidebar opts into `Projects`; `TestRendererRenderFrameWithTitleKeepsDefaultWhenTitleEmpty`; `TestRendererRenderFrameWithTitleUsesTitlebarRow`; `TestRendererContentLayoutWithTitleReservesTitlebarRow`; `TestNativeInteractiveRendersOptionalTitlebar`; `TestSwitchCommandNativeSidebarSetsTitle` |
51
+ | optional native titlebar | native picker popup frame | Covered for empty-title compatibility and opt-in titles | `picker.Options.Title`; `RenderFrameWithTitle`; `ContentLayoutWithTitle`; empty titles keep the prior frame unchanged, non-empty titles render in a picker-owned section below the top border, titlebar text/divider/chip gaps inherit the frame background/foreground instead of a separate titlebar overlay ANSI layer, and the native Alt-1 project sidebar opts into `Projects`; `TestRendererRenderFrameWithTitleKeepsDefaultWhenTitleEmpty`; `TestRendererRenderFrameWithTitleUsesTitlebarRow`; `TestRendererContentLayoutWithTitleReservesTitlebarRow`; `TestNativeInteractiveRendersOptionalTitlebar`; `TestSwitchCommandNativeSidebarSetsTitle` |
51
52
  | redraw flicker/top clipping | keyboard navigation in exact-height tmux popup | Partially covered | native redraws use synchronized updates plus coalesced row diffs after the first frame, skip unchanged frames, render frame diffs before sidebar focus commands, frame rendering avoids trailing bottom-border CRLF, and screen exit clears the alternate buffer before restore; `TestNativeInteractiveWrapsRedrawsInSynchronizedUpdates`; `TestFrameUpdateRendererSkipsUnchangedFrame`; `TestFrameUpdateRendererCoalescesEachFrameUpdate`; `TestRendererRenderFrameUsesCRLFRowsForRawTTY`; `TestNativeInteractiveUsesAlternateScreen` |
52
53
 
53
54
  ## Native Surface Architecture
@@ -72,9 +73,11 @@ native picker engine and is not a public dependency-policy change.
72
73
  ## Frame Chrome ANSI
73
74
 
74
75
  Native picker frame chrome is normalized in `internal/ui/projmuxpicker`.
75
- Titlebars restore the titlebar style after embedded ANSI resets, search prompt
76
- and footer separators fill the available frame width, and header, row, footer,
77
- and preview lines close any active SGR style before padding or frame borders can
76
+ Titlebar text, title dividers, and chip-strip gaps inherit the frame
77
+ background/foreground instead of applying a second titlebar overlay ANSI layer;
78
+ chip bodies can still carry active/inactive/disabled tones. Search prompt and
79
+ footer separators fill the available frame width, and header, row, footer, and
80
+ preview lines close any active SGR style before padding or frame borders can
78
81
  inherit it. This phase does not add popup modes or change the `popup-toggle`
79
82
  contract; native popups still rely on the existing borderless tmux popup path.
80
83
 
@@ -31,13 +31,15 @@ make npm-pack
31
31
  or:
32
32
 
33
33
  ```bash
34
- scripts/package-npm.sh --version 0.4.0 --out /tmp/projmux-npm --pack
34
+ scripts/package-npm.sh --version 1.2.3 --out /tmp/projmux-npm --pack
35
35
  ```
36
36
 
37
37
  The script stages package directories under `dist/npm` by default. It builds
38
38
  the Go binary for each supported platform, copies package metadata and docs,
39
- updates package versions in the staged copies, then runs `npm pack --dry-run`
40
- when `--pack` is set.
39
+ updates package versions in the staged copies, generates root
40
+ `optionalDependencies` for the supported platform packages using the same
41
+ version, verifies the staged metadata is internally consistent, then runs
42
+ `npm pack --dry-run` when `--pack` is set.
41
43
 
42
44
  ## Publish Order
43
45
 
@@ -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
 
@@ -15,13 +15,21 @@ view-first layout:
15
15
  actions live inside that view.
16
16
  - `Settings > Project Picker > Project Root` shows effective and saved values
17
17
  first, then the edit actions, then the explanatory hints.
18
- - `Settings > Keybindings` is the single entry point for keybinding work. The
19
- page is split into four chips: `Bindings`, `Diagnostic`, `Probe`, and `Init`.
20
- - `Settings > Keybindings > Bindings` is a keybinding discovery surface, not
21
- only a launch-toggle editor. It must show `Toggle Project Sidebar` with the
18
+ - `Settings > Keybindings` is a single action list plus a simple action detail.
19
+ It does not expose `Bindings`, `Diagnostic`, `Probe`, or `Init` as first-class
20
+ chips/tabs in the Settings root flow.
21
+ - `Settings > Keybindings` is a keybinding discovery surface, not only a
22
+ launch-toggle editor. It must show `Toggle Project Sidebar` with the
22
23
  guaranteed `Alt-1` / `M-1` default, plus sidebar-local commands, picker-local
23
24
  commands, `Pane navigation`, `Window navigation`, and `Rename` groups or
24
25
  equivalent searchable rows.
26
+ - `Settings > Keybindings > Action` keeps the user-facing edit path small:
27
+ action, current keybinding/aliases, `Add alias`, and reset. It does not offer
28
+ replace-primary, disable-default, typed-fallback, terminal mapping preview, or
29
+ terminal mapping apply rows.
30
+ - Terminal delivery remediation lives outside Settings primary flow. The
31
+ supported order is `projmux shell` first, then `projmux setup`, then
32
+ `projmux init` for supported terminal adapters.
25
33
  - Rows that cannot safely be edited still stay visible. Mark diagnostic-only
26
34
  rows with the delivery path and reason instead of hiding them or turning them
27
35
  into unsupported editable aliases. Transport-dependent rows stay visible with
@@ -55,8 +63,8 @@ view-first layout:
55
63
  `${XDG_CONFIG_HOME:-$HOME/.config}/projmux/ai-hook-actions.json`. It does not
56
64
  edit catalog `install` values or run agent install/remove commands.
57
65
  - `Settings > Labs` keeps experimental toggles, but keybindings no longer have a
58
- visible Labs row. The hidden compatibility action still redirects to the
59
- unified Keybindings page.
66
+ visible Labs row. The hidden compatibility action redirects to the
67
+ `Settings > Keybindings` action list, not to a diagnostic default.
60
68
  - `Settings > Labs > Project Hooks` is overview-first. The Labs root opens the
61
69
  overview, and the on/off mutation rows live one level deeper.
62
70
  - `Settings > AI Settings` is view-first. The root contains `Default split
@@ -71,6 +79,17 @@ view-first layout:
71
79
  and `Notify icon` details. Each detail shows the current mode plus
72
80
  immediately selectable off/symbol/emoji preview rows. There is no separate
73
81
  `Change` page for icon decoration.
82
+ - `Settings > Appearance` also shows a read-only `Theme font` status row.
83
+ `font_family` and `font_size` are desired values from the effective
84
+ project/global theme, and unsupported terminal paths must say `not applied`
85
+ instead of implying tmux changed the font.
86
+ - `Settings > Appearance > Language / Locale` is the global/user language
87
+ detail. The root row shows the saved `[ui].locale` value and the currently
88
+ effective locale. The detail shows `Current`, `[ui].locale`, optional
89
+ `PROJMUX_LOCALE` env override, and direct choices for `auto`, `en-US`, and
90
+ `ko-KR`. When `auto` is active it must show the detected source (`LC_ALL`,
91
+ `LC_MESSAGES`, `LANG`, or fallback). Unsupported locale tags must remain
92
+ visible as warnings and fall back to `en-US`.
74
93
 
75
94
  Hooks remain the reference pattern for this IA:
76
95
 
package/docs/statusbar.md CHANGED
@@ -33,6 +33,25 @@ row 1 [#S] #{pane_current_path} ⎈ <ctx>/<ns> <git>  %H:%M
33
33
  clicks on tmux 3.4+, so a `run-shell` handler can't recover the
34
34
  target after the fact — the Go dispatcher's
35
35
  `isWindowListRangeToken` fallback is now defense-in-depth only.
36
+ Each window tab reserves a one-cell live pane attention prefix from
37
+ `projmux attention window #{window_id}` before the index. AI panes use the
38
+ semantic `@projmux_ai_badge_kind` first: approval/input-required panes use
39
+ the action-required amber-orange role, response-complete panes use the
40
+ non-critical success green role, and in-progress panes use the progress
41
+ yellow role. Red/critical is reserved for error, failure, and risk chrome;
42
+ permission or input-required status badges do not use it.
43
+ Legacy busy/reply title and attention-state markers remain the fallback, and
44
+ no-state windows render a blank placeholder so title alignment and click range
45
+ width stay stable. This live window-list badge is independent from the row-0
46
+ notify queue segment and from notify queue severity or desktop notification
47
+ urgency. For example, an approval request may remain a critical queued
48
+ notification while its live status badge renders action-required amber-orange.
49
+ Window-list badges and app pane-border badges use the same semantic priority,
50
+ with display style controlled by Settings > Appearance > AI badge style and persisted in
51
+ `~/.config/projmux/ai-badge-style`. The default is `dot`; `emoji` renders
52
+ `⏳` for approval/input-required, `✅` for response-complete, and `🔄` for
53
+ in-progress. `off` (also accepted as `minimal` when read from disk) preserves
54
+ the same spacing without drawing a marker.
36
55
  The session, pwd, kube, and git segments on this row are wrapped
37
56
  in `#[range=user|<id>]` ranges and dispatched through the projmux
38
57
  handler. The standalone config also wraps the right-side `projmux`
@@ -86,13 +105,19 @@ The notify segment renders the newest queued item as a single notification
86
105
  block: project, state (`NEED`/`INFO`/`WARN`/`CRIT`), optional agent, text,
87
106
  age, and `+N` for older pending entries. Window/pane ids are not shown in the
88
107
  compact status segment.
108
+ The compact age text is locale-formatted through `internal/i18n` (`2m ago` in
109
+ `en-US`, `36초 전` in `ko-KR`). AI notify body text uses catalog-owned category
110
+ labels while preserving agent names, commands, paths, URLs, and provider
111
+ payload excerpts. The `+N` older-entry count remains a numeric compact badge so
112
+ it does not expand the status segment.
89
113
  When the notify block is wider than its cell budget, clipping shrinks the body
90
114
  text first and appends an ellipsis while preserving project, state, agent, age,
91
115
  and count metadata. If the segment is still too wide, the age is dropped next
92
116
  while badges and the `+N` count stay visible when possible. Very narrow widths
93
- fall back to the severity-colored dot plus clipped text and count; the final
117
+ fall back to dotless clipped text plus the `+N` count when it fits; the final
94
118
  hard-truncate path still closes with `#[default]` so later status segments do
95
- not inherit notification styling.
119
+ not inherit notification styling. That dotless narrow fallback applies only to
120
+ the queued notify segment, not to the separate window-list live attention badge.
96
121
  `usage` opens a native-framed detail HUD for the compact usage bar. It reads
97
122
  the cached usage state in-process, keeps the existing `projmux usage` CLI
98
123
  output shape unchanged for external consumers, aligns model/window rows with
@@ -186,6 +211,9 @@ updates the matching live tmux option
186
211
  (`@projmux_statusbar_decoration_cwd`, `_git`, or `_notify`) when run inside
187
212
  tmux. The legacy `~/.config/projmux/statusbar-decoration` and
188
213
  `@projmux_statusbar_decoration` remain fallback defaults for older configs.
214
+ Appearance also shows the effective desired theme font. This is a status row,
215
+ not a font editor: tmux status strings and ANSI output cannot force terminal
216
+ font family or size, so unsupported environments report `not applied`.
189
217
 
190
218
  To add a new clickable segment:
191
219
 
package/docs/testing.md CHANGED
@@ -54,3 +54,125 @@ desktop shell, or OS integration:
54
54
 
55
55
  Keep those checks as manual or host-run smoke validation until a dedicated
56
56
  host harness exists.
57
+
58
+ Use this smoke checklist when a change touches terminal delivery, host desktop
59
+ notifications, or reviewer confidence around those boundaries. If the change
60
+ does not touch those areas, copy the PR-note block below and mark the relevant
61
+ rows `not run`.
62
+
63
+ ### Terminal Key Delivery
64
+
65
+ Run the raw key probe outside tmux, in the terminal emulator being claimed:
66
+
67
+ ```sh
68
+ projmux setup --timeout 10s
69
+ ```
70
+
71
+ Observe:
72
+
73
+ - `Alt-1` through `Alt-5` report `OK plain`. These are the guaranteed
74
+ zero-config launch defaults.
75
+ - If a guaranteed key reports `MISS timeout`, preview a supported terminal
76
+ mapping with `projmux init ghostty` or `projmux init windows-terminal`,
77
+ apply it with the same command plus `--apply`, restart that terminal if
78
+ required, and rerun `projmux setup --timeout 10s`.
79
+ - Optional direct aliases and transport-dependent chords may be reported by
80
+ the probe, but they are not part of the guaranteed host smoke unless the PR
81
+ explicitly changes them.
82
+
83
+ Then run the app in the same terminal:
84
+
85
+ ```sh
86
+ projmux shell
87
+ ```
88
+
89
+ Observe:
90
+
91
+ - `Alt-1` opens the project sidebar.
92
+ - `Alt-2` opens the notification sidebar.
93
+ - `Alt-3` opens the existing-session picker.
94
+ - `Alt-4` opens the AI split picker.
95
+ - `Alt-5` opens Settings.
96
+ - Pressing the same launch key again closes the popup instead of typing escape
97
+ bytes into the shell or picker input.
98
+
99
+ ### WSL Toast
100
+
101
+ Run this from WSL with Windows Terminal available. The detached tmux server is
102
+ intentional: `projmux focus` falls back to the product desktop notification
103
+ path when there is no attached client to switch.
104
+
105
+ ```sh
106
+ sock="${TMPDIR:-/tmp}/projmux-host-smoke.sock"
107
+ tmux -S "$sock" kill-server 2>/dev/null || true
108
+ tmux -S "$sock" new-session -d -s projmux-host-smoke 'sleep 600'
109
+ PROJMUX_DESKTOP_NOTIFY_MODE=notify \
110
+ projmux focus --socket "$sock" --target projmux-host-smoke --json
111
+ tmux -S "$sock" kill-server
112
+ ```
113
+
114
+ Observe:
115
+
116
+ - The JSON includes `"ok":true`, `"dispatch":"notify-only"`, and
117
+ `"reason":"no-attached-client"`.
118
+ - Windows shows a short projmux toast with `session ready:
119
+ projmux-host-smoke`.
120
+ - No visible PowerShell or console window remains open after the toast.
121
+
122
+ If the PR changes click-to-focus behavior, repeat with
123
+ `PROJMUX_DESKTOP_NOTIFY_MODE=raise`, click the toast, and record whether the
124
+ host terminal returns to the target. Otherwise leave click callbacks marked as
125
+ manual/not run.
126
+
127
+ ### macOS GUI Notification
128
+
129
+ The built-in desktop sender is Linux/WSL-oriented. On macOS, smoke the
130
+ documented `PROJMUX_NOTIFY_HOOK` escape hatch with an `osascript` sender:
131
+
132
+ ```sh
133
+ hook="${TMPDIR:-/tmp}/projmux-macos-notify.sh"
134
+ cat >"$hook" <<'SH'
135
+ #!/bin/sh
136
+ title=${1:-projmux}
137
+ body=${2:-}
138
+ osascript \
139
+ -e 'on run argv' \
140
+ -e 'display notification (item 2 of argv) with title (item 1 of argv)' \
141
+ -e 'end run' \
142
+ "$title" "$body"
143
+ SH
144
+ chmod 0755 "$hook"
145
+
146
+ sock="${TMPDIR:-/tmp}/projmux-host-smoke.sock"
147
+ tmux -S "$sock" kill-server 2>/dev/null || true
148
+ tmux -S "$sock" new-session -d -s projmux-host-smoke 'sleep 600'
149
+ PROJMUX_NOTIFY_HOOK="$hook" \
150
+ projmux focus --socket "$sock" --target projmux-host-smoke --json
151
+ tmux -S "$sock" kill-server
152
+ ```
153
+
154
+ Observe:
155
+
156
+ - The JSON includes `"ok":true`, `"dispatch":"notify-only"`, and
157
+ `"reason":"no-attached-client"`.
158
+ - macOS shows a Notification Center banner with `session ready:
159
+ projmux-host-smoke`.
160
+ - If macOS prompts for notification permission, record that state in the PR
161
+ instead of treating the product command as verified.
162
+
163
+ ### PR Note Template
164
+
165
+ ```markdown
166
+ Host-only smoke validation:
167
+
168
+ - Docker-covered checks: `make test-integration`, `make test-install-smoke`,
169
+ and `make test-e2e` cover portable Linux tmux/config/notify behavior only.
170
+ - Terminal key delivery: not run / run on <terminal>; `projmux setup --timeout
171
+ 10s` showed <result>; `Alt-1..5` app popup smoke <passed/failed/not run>.
172
+ - WSL toast: not run / run on <Windows + WSL distro>; detached-focus smoke
173
+ produced `dispatch=notify-only`; observed <toast/no toast/notes>.
174
+ - macOS GUI notification: not run / run on <macOS version>; hook smoke
175
+ produced `dispatch=notify-only`; observed <banner/permission prompt/notes>.
176
+ - Desktop notification click callbacks: not run unless this PR changes
177
+ click-to-focus behavior; result <notes>.
178
+ ```