* fix(cache): refresh serve daemon's cache from disk each render cycle
The `serve` daemon (used by streaming mode) loads the on-disk Session
and Device caches once at startup and only flushes them back on exit,
so a write from a separate one-shot process was invisible to the
running daemon until it restarted, and could even be clobbered by the
daemon's own stale copy on shutdown. This affected `oh-my-posh toggle`
(Session store) as well as `enable`/`disable` - e.g. `enable reload`,
which config.Get in prompt.New checks to bypass its own config cache
after an edit (Device store). Fixes#7758.
Add cache.Refresh(), which re-syncs the in-memory store from disk when
the file's mtime has advanced, merging entries by Timestamp so a value
the daemon has itself set more recently than the file always wins.
Call it for both stores at the start of every render cycle in
serve.go, and once more before close() persists a dirty store, closing
the shutdown clobber window too. Drop the Session-only guard on the
mtime bump in store.close() so a Device write reliably updates the
file's mtime on Windows as well (the mmap-backed write path doesn't do
this on its own).
Also documents the residual limitation (there's still a narrow window
where a write can land mid-cycle) in the streaming docs.
* fix(cache): reorder store fields to satisfy fieldalignment
Adding mtime widened the struct's pointer-scannable prefix from 40 to
56 bytes (the time.Time's trailing *Location pointer landed after the
three trailing bools). Reorder so pointer-bearing fields lead and the
bools trail, matching what CI's fieldalignment check expects.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Adds two cursor_style values that only touch shape reset (CSI 0 SP q)
and blink mode (CSI ? 12 h/l), instead of selecting one of the fixed
DECSCUSR shapes. Some terminals - Windows Terminal among them - offer
cursor shapes with no DECSCUSR equivalent (vintage/thick underscore,
double underscore, empty box). Existing cursor_style values always
override those with block/underline/bar, but default_steady and
default_blinking let the terminal profile's own shape stand while
still controlling steady vs. blinking.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Renders cursor_style through the trusted template engine before
emitting the DECSCUSR sequence, mirroring the existing pwd and
console_title_template config options. This lets the cursor shape
vary per render, e.g. by branching on .Env.POSH_VI_MODE (set by the
vimode segment's shell hooks) to change shape between vi modes,
without introducing a dedicated config field.
Closes#7767
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Terminal control-rune stripping (edcf3c88, 16bf1387) closed an
injection vector but also blocked literal cursor-shape escapes users
previously put in templates (see discussion #7742). Render DECSCUSR
sequences natively instead, driven by an enum config value, the same
way iterm_features and pwd already expose trusted terminal features.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: afc824f2ed92
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.
Spawning git for status costs 22-25ms in process startup alone on
Windows, roughly 70% of the segment's render time. Computing the same
porcelain v2 counts in-process (go-git plumbing for index, gitignore
and object access; custom stat-cache worktree scan with a cache-tree
fast path) removes that tax: 6ms vs 36ms on this repository.
Opt-in via the native_status option. Any unsupported repository shape
(sparse or split index, reftables, submodules) falls back to the git
CLI automatically. Parity with the exec path is enforced by tests that
compare both engines against real repositories, including merge
conflicts, linked worktrees, and index.skipHash zero trailers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
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
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
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
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
`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
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
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
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
The gallery spawns oh-my-posh once per theme and the segment previews
once per segment: 241 processes to draw pictures the studio already
draws in a browser tab, from the same render.SVG.
A Node module instantiates that same wasm once and renders every prompt
in-process. It also decides what the SVG encoder has to stay reachable
from: with this, nothing outside the module needs to draw one.
Two things it needed. An empty config now means the built-in default,
matching what config.Load does with no path, so the homepage can ask for
the prompt someone sees before configuring anything - there is no file
to point at and no bundled theme that matches it. And the data fixture
carries what a template calls as well as what it reads, re-recorded
through the writers so its curated values survive.
The generators still shell out to the CLI. Rendering all 124 themes
through the module leaves 16 whose templates fail against the fixture,
and that is not understood yet - the keys those templates name are all
present. Switching them over waits on that.
Entire-Checkpoint: b83e4917be12
The studio renders a config from recorded data. It has no filesystem, no
processes and no network, so every segment's detection finds nothing the
moment it runs - and yet all 118 implementations were linked into the
module, because a package-level map holds a constructor for each and an
init gob-registers them all. Both are reachable from any config parse,
so between them they pinned every segment package and everything those
pull in: cloud SDKs, HTTP clients, the lot. 4.2MB raw, 0.5MB brotli, for
code no visitor can reach.
The registry moves behind a build tag. The browser build constructs no
writer at all and renders from the recorded data instead, which carries
what a template reads - fields and method results alike. Seven places
assumed a writer exists; they now ask whether there is one, and the
rendered text has somewhere to live when there is not.
The website's own data file moves to the recorded envelope format, which
is what carries a segment's enabled state when there is no writer left
to ask.
Combined with the HTTP stack already gone from this build: 29,351,525
bytes to 21,857,604 raw, 4,675,591 to 3,640,973 brotli - 22% off what a
visitor downloads.
Entire-Checkpoint: da3ad3698634
The badge linked to a listing that is no longer the recommended way to
install on Windows; winget is. Removing it takes the two SVG assets
with it.
Entire-Checkpoint: fdf9cbbb3842
The site asked visitors to read a wall of segment pages to find out
what oh-my-posh could do, and to install it before seeing a prompt.
The homepage now leads with the install command and a rendered prompt.
Every one of the 117 segments is in a searchable catalog generated from
the docs themselves, so it cannot drift from what ships, and each
segment page carries a preview rendered from committed sample data at
build time. The theme gallery inlines the SVGs from the previous
commits instead of loading screenshots.
The studio renders a config live in the browser through the wasm
module, and each segment page embeds the same editor: change the
sample config, watch the prompt redraw, then send the segment to the
studio to build a full theme out of it. Nothing is sent anywhere.
The docs move into one shell around all of it, on a single type and
spacing scale, with a filterable sidebar.
Entire-Checkpoint: dc6fc3bdbdf2
Rendering a prompt meant going through the CLI: cobra flags, a terminal
UI, an environment that probes git and cloud tooling on every segment.
Nothing else could ask for a rendered prompt, and nothing could ask for
one that touches nothing.
A render package takes a config, data and options, and returns the run
stream. The CLI becomes one caller of it; a WebAssembly entry point
becomes another, which is what lets a browser render a prompt with no
server and no machine behind it.
DataOnly moves onto the environment rather than sitting beside it as a
flag the call sites had to remember to check. A data-only render still
derives what it can from the data it was given instead of reporting
every segment absent, so a recorded file renders the same prompt it
recorded.
The terminal UI leaves the core packages so the wasm build does not
pull a TUI toolkit into the browser bundle, and the retired PNG
renderer's image package and its dependencies go with it.
Entire-Checkpoint: 603a21cafec5
The PNG exporter rasterized the prompt itself: it re-parsed the ANSI
string, resolved fonts, drew glyphs, and blurred a drop shadow, all to
produce a bitmap that could not be searched, selected, restyled or
scaled. It also carried image and font dependencies for that one
command.
A new svg package encodes the run stream from the terminal writer
(see the previous commit) into a terminal window drawn as vectors:
rounded frame, traffic lights, and one <text> per styled run. Text
stays text.
Everything after the first cut is a rendering fix found by comparing
real themes against what a terminal shows: transparent runs painted as
a cutout rather than a hole, wide glyphs given the room they ink, the
grid taken from the font's own metrics instead of the retired
renderer's picture, template whitespace preserved, the Mono Nerd Font
variants asked for first so icons advance one cell, the cursor placed
where the shell leaves it rather than after the right prompt, short
hex colours resolved, and a segment background ending where its
separator does. The canvas is now the window itself, with no
transparent gutter around it.
BREAKING CHANGE: `oh-my-posh config export image` writes SVG only. The
`--author`, `--background-color`, `--cursor-padding`, `--rprompt-offset`
and `--settings` flags are gone, as is PNG output; a command line using
them now fails rather than quietly producing something different.
Entire-Checkpoint: f6059ae8893a
The prompt was only ever emitted as a finished ANSI string. Anything
that wanted to draw it some other way had to parse those escape
sequences back out, which is why the image exporter carried its own
half-interpreter of them.
The writer now records what it emits as a stream of styled runs: the
text, the cell count, and the colours a segment actually asked for
rather than the ones already resolved to escapes. Emitting ANSI is one
consumer of that stream; anything else can be another.
Getting there meant fixing what the string form had been hiding.
Hyperlink state leaked between writes, plain mode ignored hyperlinks,
and visible-cell counting disagreed with how the runes render, which
matters the moment something lays them out on a grid instead of letting
the terminal do it. Gradients now resolve only the colours they use.
A golden harness covers all 124 bundled themes against one shared,
sanitized --data fixture: a manifest of per-theme hashes so a change
touches one line, plus full ANSI blobs for a representative handful.
Recorded replay had to become hermetic first, or the fixture would not
have been reproducible.
Entire-Checkpoint: d27890fba3f4
Removed doc/inline comments that only paraphrased the name or next
line of code, across Go source, shell init scripts (bash, zsh, fish,
pwsh, nu, elvish, xonsh, yash, Clink lua), and website JS. Comments
encoding non-obvious workarounds, race conditions, or external bug
references were kept and trimmed to just that point.
Moves the minimal-comment rule from the golang skill into AGENTS.md
so it applies repo-wide instead of to Go only.
Entire-Checkpoint: 7a66b31a1ad7
A single-color gradient effect for segments too narrow for a real
two-stop gradient to render as anything but a solid color:
dark-gradient(#color) runs from that exact color to a darker
shade, light-gradient(#color) to a lighter one.
The shade drops (or raises) HCL lightness only, by a fraction of
the base color's own headroom toward the target end, keeping hue
exact and walking chroma down only as far as the sRGB gamut
forces - blending toward black/white directly pulls chroma down
with it, reading as the color going muddy rather than
deepening/brightening. The reference delta is tuned so a 3-cell
segment - the common case this exists for - lands its shade close
to a just-noticeable-difference rather than a hard color swap; a
width multiplier grows that delta for wider segments as a
saturating curve (not linear growth with a hard clamp - that hit
its clamp by ~15-20 cells and crushed every wider segment to the
exact same near-white/near-black color, which is what "the
gradient stops working on a larger segment" turned out to be),
so a very wide segment still shades further than a moderately
wide one while topping out at a moderate, recognizably-still-the-
same-hue color instead of washing out to white or black.
GradientLastForCells mirrors the same shading (for a given cell
count) on the raw stop text, so powerline separators, diamond
caps, and inline color-override edges (via a new
gradientRenderCells package var in the terminal writer) line up
with the color the body actually ends on for that segment's own
width. GradientLast (no cell count - used by parentBackground/
parentForeground crossing into another segment, and wherever
width isn't known) falls back to the narrowest, gentlest shade.
WithGradientStops rebuilds a gradient string keeping its own
prefix (linear-gradient, dark-gradient, or light-gradient), so
resolving a palette-referenced stop (dark-gradient(p:teal)) keeps
its darken semantics instead of silently becoming a plain
linear-gradient - whole-string palette resolution only ever
expanded a bare "p:name" that itself resolved to a gradient,
never a "p:name" used as one stop inside a gradient literal, so a
palette-referenced stop reached GradientLast unresolved and fell
back to the raw, unshaded base color.
linear-gradient itself goes back to requiring two or more stops;
the single-stop overload it briefly had is now dark-gradient's job.
Entire-Checkpoint: 85522027570b
Foreground and background accept linear-gradient(stop, stop, ...) with
hex or palette-reference stops, interpolated per visible cell in HCL
space and emitted as truecolor escapes (256-color fallback when the
terminal lacks truecolor support). Powerline separators, diamond caps,
and parentBackground/parentForeground collapse to the matching gradient
edge so adjacent glyphs connect with the correct color.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: d84718082c10