Commit Graph
5356 Commits
Author SHA1 Message Date
GraviferandJan De Dobbeleer 2e41da6641 docs(git): clarify main worktree compatibility
Entire-Checkpoint: ef231d69c9bf
2026-08-02 17:27:34 +02:00
GraviferandJan De Dobbeleer 9cb5d64cfc fix(git): restore main worktree context lazily 2026-08-02 17:27:34 +02:00
GraviferandJan De Dobbeleer 8cd5d1c990 fix(git): support main worktree paths in WSL 2026-08-02 17:27:33 +02:00
GraviferandJan De Dobbeleer bc20068801 fix(git): share main worktree cache across worktrees 2026-08-02 17:27:32 +02:00
GraviferandJan De Dobbeleer 821599ccd9 feat(git): expose main worktree path
Gravifer Tokyo demonstrates the PowerShell workaround this property replaces:
https://github.com/JanDeDobbeleer/oh-my-posh/discussions/7723

The helper resolves linked-worktree indirection, runs the porcelain query, and
caches the result for the shell session. This change moves the capability behind
lazy template access and shares the cache by common repository.
2026-08-02 17:27:32 +02:00
allcontributors[bot]andJan De Dobbeleer 66cb698b71 docs: update .all-contributorsrc [skip ci] 2026-08-02 16:41:02 +02:00
allcontributors[bot]andJan De Dobbeleer fc579e0990 docs: update website/docs/contributors.md [skip ci] 2026-08-02 16:41:02 +02:00
477acf0fbe feat(studio): open config in configurator
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-02 16:40:20 +02:00
fa118cf2d0 ci(size): comment size report from a workflow_run job
The pull_request-triggered workflow gets a read-only token on fork
PRs, so posting the size comment failed with 403. Build jobs now only
upload artifacts; a separate workflow_run-triggered workflow with
write permissions downloads them and posts the comment. The PR number
travels via an untrusted artifact, so the report validates it and
checks the PR head SHA against the triggering run before commenting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 302a325be262
v30.2.0
2026-08-02 12:44:04 +02:00
42b9f83fc4 fix(studio): drop stray text under segment preview
A leftover `)}` outside the JSX expression rendered as literal text
below every segment doc's sample preview.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: e8444e478272
2026-08-02 12:37:05 +02:00
8a3a06f611 feat(config): offer pwd OSC values in schema
The terminal writer accepts osc99, osc7 and osc51 (anything else falls
back to osc99), but schema.json typed pwd as a bare string - so neither
the website editor nor schema-aware editors (VS Code, yaml-language-
server) could offer the values. Same lenient enum-or-string shape the
segment style property already uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 2b5eecac4b41
2026-08-02 12:37:05 +02:00
c53ff0de67 feat(studio): rebuild config editor on CodeMirror 6
The hand-rolled completion engine could not complete YAML key
positions: its cursor-marker probe produced invalid YAML mid-document,
the parser folded the marker into the next line's key, and the popup
never opened. Instead of patching the probe, replace the editor
foundation: CodeMirror 6 provides the popup, gutter, hover, and
tooltip machinery natively.

codemirror-json-schema was evaluated for the schema features and
rejected: enum completion through $ref resolves to nothing, $ref
nodes with sibling keywords crash its resolver, and both its published
ESM and CJS builds are broken under strict module resolution. The
proven schema resolver from the previous engine stays, rewired as a
native completion/hover source that walks the lezer syntax tree
instead of scanning text - which makes mid-edit states (blank lines,
dangling keys, open strings) work in both JSON and YAML, including
per-segment-type options completion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 9045963ce7e2
2026-08-02 12:37:05 +02:00
Jan De DobbeleerandJan De Dobbeleer db6ea0effc test(template,terminal): drop tests that assert implementation details
TestRestrictedFuncMapExactKeySet pinned a 190-entry hardcoded list that
just mirrors the map's own construction data; the drift-guard and
dangerous-funcs-excluded tests already cover the real invariants, so it
only added maintenance burden without extra protection.

TestStripControlRunesFastPath asserted unsafe.StringData pointer
identity to prove the no-allocation path ran — a memory-layout detail,
not behavior. The unchanged-input case is already covered functionally
by TestStripControlRunes's unicode case.
v30.1.1
2026-08-01 15:55:08 +02:00
Jan De DobbeleerandJan De Dobbeleer 7bfd50b858 fix(terminal): satisfy fieldalignment in iterm test cases
CI's fieldalignment linter flagged the anonymous test-case struct as
larger than necessary; reorder Features (a slice) after the string
fields to match its expected layout.
2026-08-01 15:55:08 +02:00
Jan De DobbeleerandJan De Dobbeleer 16bf138780 fix(terminal): strip control runes from OSC pwd/iterm sequences
write's per-rune isControlRune filter only guards segment-body text
streamed through it. Pwd() and RenderItermFeatures() format the raw
current working directory (and username/hostname) directly into OSC
7/51/99/1337 sequences via fmt.Sprintf, bypassing that filter entirely.
A directory name containing ESC/ST/BEL bytes closes Oh My Posh's own
OSC sequence early and lets an independent, attacker-controlled escape
sequence stand on its own once the victim cds into it and the pwd or
iterm_features integration renders it — the same class of injection an
earlier fix closed for segment content, reopened via a sink it didn't
cover.

Add a whole-string control-rune stripper for these one-shot OSC payload
fields and apply it to pwd/userName/hostName before every OSC format
call in both functions.

Entire-Checkpoint: 97966247c0a5
2026-08-01 15:55:08 +02:00
Jan De DobbeleerandJan De Dobbeleer 9edb5b7a3a fix(template): scope env var access to trusted templates only
context.init bound Getenv to the real environment regardless of trust
level. patchTemplate rewrites .Env.NAME into (call .Getenv "NAME"), but
that rewrite isn't the only way in: a raw {{ call .Getenv "NAME" }} in
untrusted text reaches the same binding directly, so restricting the
rewrite alone would still leave environment values readable from
attacker-controlled input (e.g. a directory name), letting secrets leak
into the rendered prompt.

Bind Getenv only when the template is trusted; untrusted renders get a
stub that always returns empty. Empty rather than nil keeps templates
that legitimately reference .Env.* from failing to render entirely.

Entire-Checkpoint: cf47aaae6d2a
2026-08-01 15:55:08 +02:00
Jan De DobbeleerandJan De Dobbeleer 7e224b23ad fix(template): block network and DoS-capable functions from untrusted templates
RenderUntrusted's func map was built by copying every sprig function
except an explicit six-name blocklist, so getHostByName (a live DNS
lookup) and over a dozen CPU-expensive crypto functions stayed reachable
from attacker-controlled text such as a directory name. getHostByName
let a crafted folder name exfiltrate .Env values over DNS the moment the
prompt rendered; the crypto functions let the same input hang the shell
via a loop.

Block both classes in dangerousFuncs, keep them available to trusted
config templates via sharedFuncMap, and replace the untrusted map's
"copy everything except six names" construction with an explicit,
independently maintained allowlist of the sprig functions already
reviewed as safe for untrusted input. A drift-guard test now fails when
a sprig upgrade introduces a function that isn't yet triaged into either
list, so new capabilities can no longer reach untrusted templates by
default.

Entire-Checkpoint: 6beb436f6cd8
2026-08-01 15:55:08 +02:00
2002b84594 feat(studio): lightweight editor feature in JSON
Show a description tooltip on hover for completion options (real
prose text, or a "View documentation" link for URL-only schema
descriptions), and flip the completion popup above the caret when
there isn't enough viewport room below, clamping its max-height so
it always stays fully on-screen.

Also remove the entire.json repo hook, whose sh invocation errored
on this machine and blocked every gated tool call.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: da7937bafeeb
v30.1.0
2026-08-01 11:21:47 +02:00
234a5d7e29 style: avoid else and reword stale cobra comments lost in rebase
The rebase onto origin/main dropped 4 commits whose content already
landed upstream under different hashes, taking with them style fixes that
had been folded into those commits earlier in this branch's history:

- color/shade.go, svg/svg.go, prompt/golden_test.go, terminal/writer.go:
  re-apply the avoid-else refactors for in-scope hunks introduced by this
  branch's styled-runs/shade/svg work.
- config/dsc_hook.go, cli/dsc/shell.go: reword the stale cobra mentions
  back to the command tree/flag machinery wording used elsewhere on this
  branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: c61e99a57d85
2026-07-31 07:40:24 +02:00
f4874c8260 docs(cli): drop stale cobra/mousetrap wording from pre-existing comments
statusline.go's doc comment and root.go's Explorer-launch-guard comment
predate this branch and weren't touched by its cobra/pflag -> cmdtree/cmdflag
rename commits, so they were left referencing the removed dependency and its
'mousetrap' terminology. Reword them now that cmdtree/ExplorerLaunchHelpText
are the only names that exist.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: d987e21b8f4f
2026-07-31 07:40:24 +02:00
Jan De DobbeleerandJan De Dobbeleer c8adbd0904 ci: add non-blocking release binary size check for PRs
Builds a release-equivalent binary for every major OS we support
(linux, windows, darwin; amd64), using the same ldflags/tags/env as
src/.goreleaser.yml, each cross-compiled on ubuntu-latest with
CGO_ENABLED=0 (mirroring how build_code.yml already cross-builds every
OS via goreleaser). Each platform can pick up its own dependencies, so
each is checked independently.

Compares every build against the latest published GitHub release's
matching asset, since that mirrors what users actually download and
avoids rebuilding the base branch from source. Posts (and updates, on
re-runs, via a hidden HTML marker) a single PR comment summarizing the
baseline size, PR size, and delta in bytes/percent per OS, calling out
growth beyond 256 KiB or 2%% so an accidental heavy dependency doesn't
slip in unnoticed.

The job only fails on genuine errors (build/download failures), never
on size growth, so legitimate feature additions aren't blocked.

Triggers only on pull_request changes under src/**, since that's the
only tree the binary is built from.
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 2a59ed1e50 refactor(cli): replace cobra and pflag with minimal internal packages
The CLI framework was the largest remaining third-party chunk (545 kB)
and most of its surface went unused - completions are explicitly
disabled, templates and command groups never used. Replace both with
internal packages mirroring the exact API subset in use: a command tree
with persistent flags and nearest-hook semantics, POSIX flag parsing
(--flag=value, --flag value, shorthands and grouping, the -- terminator,
interspersed positionals, unknown-flag allowlisting for the argocd
segment), positional validators, the implicit help command, and the
Windows Explorer double-click guard.

Help, usage and error output were verified byte-identical against the
previous binary across a 47-case golden battery: every command and
subcommand help screen, error formats and exit codes, flag styles
including flags before the subcommand, and init/print rendering.

Shrinks the stripped linux/amd64 binary by 586 kB (13.50 MB -> 12.91 MB;
23.7% below the original 16.93 MB).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 98f069cb9d chore: gofmt alignment after dependency swaps
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 7a4b208b97 refactor(font): replace sfnt library with minimal name table reader
Font installation only reads the name table of .ttf and .otf files - the
extension allowlist runs before parsing - yet ConradIrwin/font carried
WOFF/WOFF2 decoding and a brotli implementation along with it. Replace
it with a small reader for the sfnt table directory and name table,
decoding entries the same way (UTF-16BE for Unicode and Microsoft
Unicode platforms, Mac Roman for legacy Mac entries, raw bytes
otherwise).

Verified byte-identical metadata extraction (full name, preferred
family, family) against the previous library across 45 system TTF and
OTF fonts. Shrinks the stripped linux/amd64 binary by 377 kB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 5848391619 refactor: replace ini, modfile and jsonutil dependencies
Four dependencies served narrow slices of their APIs:

- gopkg.in/ini.v1: the aws, gcp and git segments read sections and keys.
  Replaced with a minimal src/ini package mirroring the same API subset
  and ini.v1's semantics (first key wins, inline comment cutting,
  surrounded-quote trimming, BOM handling).
- golang.org/x/mod: the go segment read one field, the go directive of
  go.mod. Replaced with a line scan.
- gookit/goutil/jsonutil: only StripComments was used. Replaced with
  text.StripJSONComments, which unlike the original is string-aware and
  won't mangle values containing comment markers.
- github.com/google/uuid: the session id in cache/init.go. Replaced with
  a local RFC 4122 v4 generator (uuid remains an indirect dependency of
  sprig's template functions).

Prompt output verified byte-identical against the previous binary,
including the git segment against this repository's live config.
Shrinks the stripped linux/amd64 binary by 266 kB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 229932c68e refactor: drop x/text cases, language and message packages
Three call sites pulled in x/text's casing and message-formatting
machinery (with its plural and number-format tables) for ASCII-only
work: title-casing segment names and taskwarrior command keys, and
comma-grouping a number in the execution time segment. Replace them
with text.Title and a local thousands-separator helper, both verified
against the exact x/text output for the inputs these paths handle,
including the compatibility-sensitive segment template names
(nix-shell -> Nix-Shell, ui5tooling -> Ui5tooling).

x/text itself stays: the terminal writer's rune width tables use
x/text/width by design.

Shrinks the stripped linux/amd64 binary by 545 kB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 09cface00b refactor(dsc): embed pre-generated resource schemas
Reflecting the DSC resource schemas at runtime linked invopop/jsonschema
and with it pb33f/ordered-map and a second YAML library (yaml/v4, next
to the yaml/v3 the config loader already ships) into every prompt
binary, for output that never changes between builds. Generate the
schemas ahead of time and embed them; golden tests regenerate the
schemas via the same reflection (test-only dependency, not linked) and
fail on drift. Regenerate with UPDATE_SCHEMAS=1 go test ./cli/dsc ./cli/font.

Output verified byte-identical for the shell, configuration and font
resources. Shrinks the stripped linux/amd64 binary by 438 kB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
ClaudeandJan De Dobbeleer 2b64976e6b refactor(terraform): replace HCL parser with minimal version scanner
The hashicorp/hcl/v2 + zclconf/go-cty stack existed to extract a single
attribute: required_version from a top-level terraform block. Replace it
with a small scanner that tracks just enough HCL structure (comments,
strings, interpolation, heredocs, block nesting) to locate that
attribute, and fails closed to the state-file fallback on anything it
cannot make sense of - the same path a full parser failure took.

Shrinks the stripped linux/amd64 binary by 1.80 MB (16.93 MB -> 15.13 MB).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
2026-07-31 07:40:24 +02:00
43b4cb3e3b fix(cli): remove stale migrate mention from config --help
The config command no longer has a migrate subcommand, but its Long
description still advertised it.

Fixes #7734

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 80471ee5a91c
v30.0.0
2026-07-30 19:44:41 +02:00
ed38295c32 fix(shell/pwsh): use empty string for CONDA_PROMPT_MODIFIER
Assigning \False stringifies to the literal False, which passes
conda's own truthiness check and gets Write-Host'ed on every prompt.
An empty string matches what conda's prompt wrapper actually expects.

Fixes #7733

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: ce0acf0d99c9
2026-07-30 19:44:41 +02:00
ClaudeandJan De Dobbeleer de41b29ace fix(battery): add illumos/solaris support to fix cross-compile failure
battery_windows_nix.go's negative build tag matched illumos/solaris
without providing a systemGetAll implementation, breaking `go build`
on those platforms. Add a dedicated no-op implementation and exclude
illumos/solaris from the fallback file.

Fixes #7730
2026-07-30 19:44:41 +02:00
b1638b1eaa docs(project-knowledge): note the autocrlf rebase gotcha on Windows
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 4e3bd1765425
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer 55e97cc3b1 docs: style code blocks accordingly
Entire-Checkpoint: 9214af96e44d
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer f6793cbedc fix(svg): change studio render based on mode
Entire-Checkpoint: 642cd14ce7ad
2026-07-30 19:44:41 +02:00
8528d35a7a feat(studio): add Download PNG button
Rasterizes the live prompt SVG to a PNG on a hidden canvas. The font
is embedded as a base64 data URI in the SVG before rasterizing, since
an Image() loaded from a blob URL cannot see the page's own
@font-face and would otherwise fall back to a generic monospace font.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0401007e6324
2026-07-30 19:44:41 +02:00
73bda6801d feat(website): switch homepage hero svg with site theme
The homepage hero rendered once, against the svg exporter's own fixed
dark canvas background, so it always looked like a dark-mode prompt
even when a reader had the site in light mode.

export_themes.mjs now renders the default config twice - once
unchanged for dark, once with --background-color=#ffffff (Infima's
own light background) for light - and hero.json carries both as svg/
svgLight. The homepage ships both renders in its static HTML and picks
between them with plain CSS keyed off Docusaurus's own
html[data-theme] attribute, the same convention custom.css's
--omp-card-background override already uses, so the switch is instant
and needs no client re-render.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: e4462dff49f1
2026-07-30 19:44:41 +02:00
5c877b6381 feat(cli): add --background-color flag to export image
The svg export's canvas background always came from the theme's own
terminal background (or a fixed dark fallback with none set) - a
caller with no way to ask for a specific canvas color at export time,
which the website needs to render the homepage hero against both a
dark and a light background for its own dark/light toggle.

The flag only ever fills in for a theme that sets no terminal
background of its own: withDefaults still prefers a real
TerminalBackground when the theme has one, so this can't paint over
how a theme would actually look in a real terminal.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0ba07d2489e2
2026-07-30 19:44:41 +02:00
76ae543452 feat(svg): replace macOS traffic lights with WT-style chrome
Redraws the terminal-window chrome to match a Windows Terminal window
instead of macOS Terminal.app: no colored traffic-light dots, a flat
title bar sharing the window's own background (no two-tone header),
thin line-drawn window controls (- box x) flush against the right
edge, and +/chevron tab controls. All chrome glyphs share one baseline
helper so the title, tab controls and window controls sit on the same
visual line regardless of their own font-size.

Adds Options.Title so a caller can label the window (e.g. the shell
name); unset renders no title, matching the prior chrome's lack of one.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0e301f956da9
2026-07-30 19:44:41 +02:00
d7caf75483 fix(svg): remove extra blank line before watermark
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 3de92a207959
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer 4db87633d3 docs(themes): add Open in Studio button
Entire-Checkpoint: 10e4ab343956
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer 267f672b96 fix(svg): render colors correctly
Entire-Checkpoint: f00095ed9b98
2026-07-30 19:44:41 +02:00
c69b9fccc3 refactor(config,shell): decouple wasm build from segments and dsc
config/config.go and config/default.go referenced bare segments.CONST option keys
directly and unconditionally, pulling the entire segments package - and its
transitive HCL/go-cty/x-mod-modfile/ini/cli-auth dependency tree - into every
binary linking config, including src/wasm. Mirror the option-key strings as
local options.Option consts in config instead and drop the segments import.

src/dsc (invopop/jsonschema -> go/ast, go/parser, go/doc; spf13/cobra) was
imported unconditionally by config/dsc.go and shell/dsc.go for CLI-only DSC
(Desired State Configuration) bookkeeping never exercised by the wasm render
path. Move both DSC resource types into a new cli/dsc package where they're
actually used, and decouple config from dsc via a nil-by-default hook -
config.NewDSCTracker, set by cli/dsc's init() - so config.Parse() no-ops the
bookkeeping instead of importing dsc directly.

Combined effect on the wasm build: 21,833,910 -> 17,825,752 bytes stripped
(-4.01MB, ~18.4%). go list -deps ./wasm/ confirms segments, dsc,
invopop/jsonschema, spf13/cobra, go/ast, go/parser, and the HCL/go-cty chain
are all gone from the link graph.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 3ef09bcd218e
2026-07-30 19:44:41 +02:00
26e06ac0bd refactor(runtime): split gopsutil/terminal-dimensions out of the js/wasm build
Move runtime.Terminal's gopsutil-backed and terminal-dimensions-backed
methods (Shell's parent-process fallback, SystemInfo, Memory, TerminalWidth,
Platform, and the remaining WSL/registry/media/connection probes) into
!js/js file pairs, mirroring the existing terminal_root_js.go /
terminal_writable_js.go split. text/template's reflect.MethodByName call
keeps every exported method of runtime.Terminal linked once it is converted
to the Environment interface (render.Config does this), so a per-method
build-tag split is the only way to drop an implementation's cost - a new,
slimmer Environment type would not help.

Retags terminal_unix.go/terminal_unix_test.go from !windows to
!windows && !js since terminal_js.go now owns those methods for js.

Verified: gopsutil (v4.26.6) already ships GOOS=js fallback stubs, so this
is not a multi-MB win - the stripped omp.wasm build drops from 21,866,822 to
21,833,786 bytes (~33 KB), and go build -ldflags=-dumpdep confirms
gopsutil/process, gopsutil/cpu, gopsutil/load, and terminal-dimensions are
fully gone from the link graph (only the type-only gopsutil/disk.IOCountersStat
used by SystemInfo.Disks remains, out of scope per environment.go).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-30 19:44:41 +02:00
48d6685d85 feat(website): live-reload themes and wasm during local dev
`npm start` now runs scripts/dev.mjs instead of `docusaurus start`
directly. It wraps the dev server with watchers that keep generated
artifacts current without a manual regen + restart:

- a /themes edit re-renders just that theme and patches it into
  generated/themes.json, so <ThemeGallery/> hot-reloads it
- editing export_themes.mjs, font-metrics.mjs or segment_data.json
  falls back to a full `npm run themes`, since those can change
  every theme`s render
- editing a Go source file under src/ rebuilds the studio`s wasm
  module (`npm run wasm`)

`npm run build` and CI are unaffected: they already run these
scripts explicitly before building and never watch anything.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 23a2e79bf51a
2026-07-30 19:44:41 +02:00
47f2228931 fix(data): render a segment the same with or without its writer
Recorded data replayed without a segment writer diverged from the same
data replayed with one, in four ways that only surfaced once both paths
rendered the bundled themes side by side:

- Colour templates resolved against the writer rather than the data, so
  every one of them fell back to the plain colour where no writer
  exists - 49 of 124 themes.
- A value that is not a struct lost its methods. battery.State is an
  int, and `.State.String` is what every battery theme switches on.
  Method results now travel in a tree of their own, which keeps the
  data itself writer-shaped: an entry whose "State" were an object no
  longer unmarshals into the writer.
- A field renamed by its json tag was recorded under its Go name alone,
  so encoding/json never matched it on the way back and terraform's
  version restored as nil. Both names are recorded now.
- `date` fell through to the wall clock for a timestamp arriving as a
  string, which is how a recorded time.Time always arrives.

The wasm build carries no zoneinfo, so its idea of local time was
whatever offset the host happened to be on and a gallery built in July
printed an hour later than the same gallery built in January. It
renders in UTC now, with recorded timestamps written to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 707298e5cc26
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer e4ac930b0c docs(website): hide the table of contents on the theme gallery
Entire-Checkpoint: e9f35f03135c
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer 3b63012568 refactor(cli)!: give fonts a list and an install command
Choosing a font was a five-stage terminal UI: fetch the list, scroll it,
download, unzip, install. It pulled bubbletea, lipgloss, bubbles and a
fuzzy matcher for what amounted to a non-filtering list of names with
one highlighted - and Go runs a linked package's init before main
whether or not the subcommand was invoked, so everyone typing at a
prompt paid for it on every render.

A CLI already has a way to answer "which fonts are there": print them.

  oh-my-posh font list
  oh-my-posh font install Meslo

The list can be grepped, piped and scripted, none of which a picker
allows, and install is what it always was underneath - resolve, fetch,
unzip - now reporting on a status line and a progress bar.

Removes 20 modules including go-runewidth, whose package init built a
2.2MB lookup table nothing in the prompt path ever read. The
atotto/clipboard fork this repo carried goes with them: bubbletea was
the only thing that needed it.

Binary 17,330,176 to 15,981,056 bytes (-7.8%). Init, median of 11 runs,
39.3ms to 6.8ms - on every invocation, prompt renders included.

BREAKING CHANGE: `oh-my-posh font install` now requires a font name; run
`oh-my-posh font list` to see them. The interactive picker is gone, and
with it the --headless flag, which existed to skip it.

Entire-Checkpoint: ef231d69c9bf
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer cc8c2add4a refactor(cli): draw the auth and upgrade UIs without bubbletea
Both were display-only shells: a spinner, a status line, and in the
upgrade's case a progress bar. No list, no input, no alternate screen,
no mouse. The upgrade's business logic was already decoupled behind
plain reporter callbacks, added so the core would never import
bubbletea, so it only ever needed a different subscriber.

The auth flows had their device-code polling inside the models. They
report through the same setState calls as before; what changed is that
Run drives them synchronously instead of spawning a goroutine to keep a
message loop fed, because a status line now redraws from its own ticker.
Each flow still words its own steps - GitHub names the device code,
YouTube Music names its base URL - so message() replaces View().

The upgrade UI loses its q-to-quit key. Interrupting an upgrade midway
through replacing a binary was never a good idea, and ctrl+c still
reaches the process.

The font picker still uses the framework, so nothing is removed from
go.mod yet.

Entire-Checkpoint: 0254bffa0c17
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer 20373f84a7 feat(cli): add a terminal UI that a prompt engine can afford
oh-my-posh renders a prompt once or twice per keystroke and then exits.
It also ships a font installer, an upgrade UI and two OAuth flows, all
built on bubbletea - and Go runs a linked package's init before main
whether or not the subcommand that needs it was invoked. Someone typing
at a prompt pays for a font picker they are not using: 1.84MB of binary
and 44ms of every render, most of it go-runewidth building a 2.2MB
lookup table nothing in the prompt path reads.

What that framework is asked to do here is small. Across the whole
binary the interactive surface is one non-filtering select list, a
spinner, a progress bar, and quit keys. No text input, no alternate
screen, no mouse.

So: a status line that repaints with a carriage return and an erase, a
progress bar that does the same and keeps reporting to the terminal's
own taskbar indicator, and a numbered picker.

Everything writes in cooked mode. Arrow keys would need raw mode, and
raw mode is the one genuinely risky thing here - termios on POSIX,
console modes on Windows, restoring both when the process panics, and a
fallback for the ptys where stdin is a pipe and none of it applies. A
number and Enter needs none of that, and works when stdin is redirected,
which is how anyone scripts an install.

Nothing uses it yet.

Entire-Checkpoint: 040a19f91be1
2026-07-30 19:44:41 +02:00
Jan De DobbeleerandJan De Dobbeleer 0116798118 fix(config): render a data-only segment the way a writer would
Two things a segment lost when there was no writer to build it.

A segment with nothing recorded about it never turned on. Enabled was
only ever set from the writer, so with none the segment fell straight to
its fallback and drew nothing - including a text segment, which needs no
data at all and whose whole content is its template. Render already
settles this correctly for every other case, by asking whether the text
came out blank, so the answer is to let it: with no writer and nothing
recorded, the template decides.

Numbers arrived as the wrong type. JSON has one number type, so
unmarshalling into a map makes every number a float64 where the writer
struct would have produced whatever the field declared. sysinfo renders
`{{ round .PhysicalPercentUsed .Precision }}`, and round wants its
precision as an int, so a Precision of 2.0 failed a template that an int
2 rendered. Whole numbers are restored as ints, which is the guess that
still renders: a function taking a float widens an int, while one taking
an int rejects a float.

The fixture guard learns that a recorded entry is an envelope, and that
a key naming a method is not the typo it hunts for. azpwsh leaves the
fixture: it is not a registered segment type, though two bundled themes
still name it.

Entire-Checkpoint: 1478bb7e734f
2026-07-30 19:44:41 +02:00