Omniscio documentation
Browse all documentation
  1. Getting Started19
  2. Sessions & Agents145
  3. Inbox & Notifications74
  4. Projects & Tasks105
  5. Automation & Scheduling78
  6. Knowledge & Memory29
  7. AI Features75
  8. Integrations118
  9. Plugins & Marketplace45
  10. Cloud & Teams56
  11. Settings & Customization83
  12. Account & Billing31
  13. Troubleshooting88
  14. CLI & API Reference29
  15. Legal & Policies5
  16. Uncategorised25

Felt-lag scorecard (typing, switching, freezes, per build)

One command prints the four numbers that describe how laggy the app feels — typing, switching, back-end freezes and blocked rendering — for one app build, and keeps a row per build so two builds can be compared. A record it could not read, or one too short to trust, says so instead of printing a calm zero.

What it is

npm run perf:lag answers one question: is the app getting easier or harder to use, build over build? The raw signals for that already exist on the app's own tapes — Event Timing on the keys you type, phased timings on every session switch, caught main-thread stalls, long animation frames — but they were spread over three files and no number was tracked build over build, so nobody could prove a fix had helped.

The four numbers, all for ONE app build:

number what it means where it comes from
composer keystroke → paint how long a keypress takes to appear on screen the [composer-keys] histogram lines in interaction.log
session switch → paint how long a switch takes until the new session is drawn the switch-to-paint phased-nav records
back-end freeze seconds per hour the app's main thread was blocked [STALL-CAUGHT] rows in heartbeat.log
renderer blocked seconds per hour the screen's own thread was blocked desktop-main [loaf] blocking rows in interaction.log

How it behaves

Run it

npm run perf:lag                      the scorecard and the trend
npm run perf:lag -- --window-min 60   a shorter window
npm run perf:lag -- --since 9:00      start the window at a given time
npm run perf:lag -- --last 12         show twelve builds in the trend
npm run perf:lag -- --no-append       read without adding a ledger row
npm run perf:lag -- --json            the whole reading as data

What it prints

perf:lag — the felt-lag scorecard, one reading per app build

BUILD    5bd31f020/9f2c1a44
launch   2026-10-31 09:12
window   2026-10-31 09:12 → 10:49 (97 min)
tracing  on

  composer key → paint         p95 184 ms · p50 61 ms · n=412
                               every keystroke from one frame (16 ms) up
  session switch → paint       p95 268 ms · p50 210 ms · n=37
                               a switch slower than 10 s is discarded before the tape
  back-end freeze              2.10 s/h · 3.4 s over 94 min covered
  renderer blocked             1.80 s/h · 2.8 s over 94 min covered
  • The window starts at the current build's own launch, so what it prints belongs to one build. Ask for a longer window with --window-min and it splits the span into one segment per launch, prints the newest, and still writes every segment to the ledger.
  • n is printed beside every percentile, and a sample below 30 reads unmeasured — the p95 of ten keystrokes is just the slowest of ten.
  • covered is printed beside every rate, because a figure scaled up from ten covered minutes is a guess, not a reading. Under ten covered minutes the row reads unmeasured.

The trend, and the ledger

Each run writes one row per build to perf-lag-ledger.jsonl in the app's data folder. The ledger holds one row per build — a second run on the same build replaces its row rather than adding another — and keeps the newest 100 builds. The trend table prints the last eight side by side:

  build                  measured at        keys p95  switch p95  freeze s/h  render s/h  tracing
  5bd31f020/9f2c1a44     2026-10-31 10:49        184         268        2.10        1.80  on

A run in which every number was unmeasured writes nothing, so the trend never fills with rows that carry no reading.

Why the keystroke number needed a new recording

The Event Timing observer the app already runs only reports an interaction once it takes 200 ms or more. The average click or key press is far below that, so a p95 computed from that tape would be the p95 of the slow tail only — a number that can look better while the app gets worse.

So the app also watches the session composer at the browser's floor (one frame, 16 ms) and counts every key press: the ones the browser reports carry their measured duration, and the ones too fast to report are counted into the first bucket rather than dropped. Once a minute it writes one [composer-keys] line carrying the histogram. A histogram, not a list of timings, because a histogram merges exactly across any number of minutes and costs the same to write however fast you type.

What that means when reading it: the keystroke p95 covers every key press of 16 ms or more, so a build where typing is genuinely instant prints p95 < 16 ms — the finish line, not a blind spot — and the share of keystrokes under one frame is printed beside it.

Where it can go wrong

  • Tracing switched off. The renderer observers — and so the keystroke, switch and renderer numbers — only run while Settings → Diagnostics → "Performance & interaction tracing" is on. With it off those three read unmeasured and the readout names the switch; every ledger row also records tracing so an on build is never compared with an off one.
  • A build from before this recording. A build that predates the composer sampler writes no [composer-keys] lines, so its keystroke row reads this build writes no composer samples — distinct from tracing being off.
  • Switches are capped at 10 seconds. The switch metric discards anything slower as a window-hidden artifact, so a window of multi-second freezes reads a good switch p95. The row says so rather than hiding it.
  • A quiet hour is not a dark one. A rate is only published when the app demonstrably covered the time; the rest of the time it reads unmeasured with the reason.
  • The tapes rotate. A window longer than what the rotated files hold reports the time it could actually see, and notes when the record stops before the window ends.

Related

  • perf-status.md — the live readout the app runs itself, with its own alarms. This command is the build-over-build view of the same machine.
  • interaction-trace.md — the tape the keystroke and switch numbers come from, including the [composer-keys] line.
  • freeze-timeline.md — explains ONE freeze in detail; this one counts them per build.

Last verified 2026-10-31