Tyler Garrett
Screenshot 2026-06-08 at 8.11.03 PM

Tyler Garrett

Founder, dev3lop

Screenshot 2026-06-06 at 9.01.20 AMScreenshot 2026-06-04 at 10.34.36 PMScreenshot 2026-06-09 at 6.55.41 AMIMG_0001Screenshot 2026-06-07 at 8.42.47 AM

Recent

general localVideo — findings Aug 5, 2026

Notes from building a local image/video generation app on Apple Silicon. Everything here was measured on one machine unless stated otherwise:

M5 Max · 128 GB unified memory · macOS 26.4 · ComfyUI 0.29.0 · torch 2.13.0 · Python 3.12.13 Models: LTX 2.3 22B (BF16) for video, Krea 2 for images.

Numbers from a single machine and a single library. They are real measurements, not benchmarks — treat them as orders of magnitude, not league tables.


1. The FP8 wall on Apple Silicon

Almost every curated model weight you can download is FP8. Metal has no float8 kernels. An FP8 tensor on the GPU cannot be converted, copied off, or even reinterpreted with .view(). It fails at load, not at inference:

RuntimeError: Undefined type Float8_e4m3fn

The same tensors work fine on CPU. There is no userspace workaround for the GPU path — you cannot cast your way out, because the cast itself is the unsupported operation.

This is why so many "local generation" tools hand a Mac user a 30 GB download that crashes on load.

The inversion that fixes it

FP8 exists to squeeze large models into 24 GB consumer NVIDIA cards. That constraint does not apply to a Mac with unified memory.

  • torch.mps.recommended_max_memory() on this machine: 115.4 GB
  • LTX 2.3 22B, uncompressed BF16: ~46 GB

So the fix is to stop compressing. Use the BF16 build the quantised one was derived from. The "compatibility" format is the problem; the "too big" format is the solution.

Blog angle: the counterintuitive one — on a big-memory Mac, the larger download is the one that works.

ComfyUI flags that matter

PYTORCH_ENABLE_MPS_FALLBACK=1 PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 \
  python main.py --use-split-cross-attention --fp32-vae
  • Never --bf16-vae — breaks the LTX audio VAE.
  • Never --force-fp16 — black frames on recent macOS.
  • PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 lifts the allocator's default ~0.75×RAM

cap, which you need for a 46 GB model.


2. Measured generation cost

JobTime
Image 512² · 1 step~17 s
Image 512² · 4 steps~20 s
Image 512² · 8 steps~42 s
Image 1024² · 8 steps~60 s
Video 2 s · draft · 640²~87 s
LTX 2.3 22B BF16 sampling~7.4 s/step @ 49 frames · ~36 s/step @ 201 frames

Fixed overhead dominates short image jobs

1 step costs 17 s and 4 steps costs 20 s. Three extra sampling steps cost 3 seconds; the job costs 17 seconds before it samples anything. Model load, text encode and VAE decode swamp the actual diffusion at low step counts.

Practical consequence: dropping from 4 steps to 1 saves ~15%, not ~75%. People optimise the wrong number.

The attention ceiling is a hard wall, not a slowdown

Refined (2× upscale) video is bounded by attention memory:

(W/32) · (H/32) · (frames/8 + 1)  >  ~45,000 tokens  →  allocation aborts

This is worth computing before submitting rather than discovering as a crash. It also means "longer" and "bigger" trade against each other on a fixed budget — you can have a long small clip or a short large one.


3. Making an extension not look like a cut

Generating a continuation from the last frame of a clip produces a visible seam. The interesting finding is why.

A single still tells the model where things are, but not where they were going. Motion restarts from rest at the join. It is not a colour or detail problem — it is a velocity problem.

LTXVAddGuide accepts a multi-frame video guide, not just one image. Feeding the last 9 frames instead of the last 1 carries velocity across the join.

Measured on the same source clip:

worst frame-to-frame jumpratio vs. clip average
single still1.632.35×
9-frame motion guide1.082.10×

Honest caveat: this did not reach the ~1.5× ratio where a cut becomes genuinely invisible. The residual difference is tone, not motion — the re-rendered frames land about 1.2/255 darker. Dissolving across the overlapping frames hides that step, and it measures better than either a hard cut or a blind crossfade, because you are blending two takes of the same instants rather than two different moments.

Guides are cropped to 8n+1 frames, so 1, 9, and 17 are the usable depths.

The "it only zooms out" mystery

Early clips all drifted backwards regardless of prompt. The cause was not prompting — it was LTXVPreprocess(img_compression), which is the motion knob. ComfyUI's default is 35; this app had shipped 18. Lower compression produced less motion, which read as a slow zoom-out.

Blog angle: a parameter named "image compression" is the motion control. The name tells you nothing.


4. Drawing a lineage graph correctly

The app draws generation history as a left-to-right DAG: an image spawns clips, a clip spawns extensions, an extension gets mended back onto its source.

The bug that makes chains "fall upward"

A tidy-tree pass placed each node, then pushed it down to clear its column — but left the descendants it had already placed behind. This is Reingold–Tilford missing its modifier/shift step. The deeper a chain ran, the further it lagged, so every chain visibly climbed away from the origin it came from.

Measured over a real 68-node library, in row units:

mean parent→child driftworst edgegroup spreadgroup off-centre
before2.6510.56.363.68
after0.411.500

The off-centre and spread going to exactly 0 is not tuning. Placing each node at the centre of the bounding block it produced — rather than the midpoint of its first and last child — makes every subtree symmetric about its own root by induction. "Centred in its grouping" falls out of the algorithm.

Recursion depth is the number of columns

A textbook recursive contour implementation blew the stack at ~5,300 columns. The non-recursive version (pre-order flatten, then read backwards for post-order) does:

  • a 5,000-column chain in 5 ms
  • an 8,191-node binary tree in under 1 ms

A generation history grows without bound, so this is not hypothetical.

Hang nodes off their deepest parent, not their first

A "mend" joins a clip and that clip's own extension — it has two parents. Hanging it off the deepest one makes the chain read left-to-right instead of doubling back. Mend edge length dropped from 1.39 → 0.32 rows mean.


5. Export formats: measure, don't estimate

For a 20.2 s clip at 896 px:

FormatSizeTime
MP4 (stream copy)0.95 MB81 ms
MP4 silent0.81 MB155 ms
WebM (VP9)0.68 MB1.8 s
WebM silent0.45 MB1.8 s
GIF @ 480px/12fps10.83 MB0.7 s
GIF ping-ponged21.61 MB1.3 s

GIF is ~15× the size of VP9 for the same seconds. A full-resolution GIF of a 20 s clip is tens of megabytes and shareable nowhere, which is why hard fps and width ceilings are the only honest option.

Why not estimate sizes from bitrate

Because GIF size depends almost entirely on how much the frames move, and generated clips range from a static lantern to a whip pan. Bitrate maths would be instant and wrong exactly where it matters. Producing all three formats and reporting real bytes costs ~1.9 s and is then cached — a confident "about 3 MB" that arrives as 11 MB is worse than a two-second wait.

VP9 is unusable at defaults

-deadline good -cpu-used 4 -row-mt 1 is the difference between 0.9 s for a 10 s clip and minutes. Worth knowing before concluding VP9 is too slow to offer.

GIF needs a generated palette

Without palettegen/paletteuse, the encoder falls back to a generic 256-colour table and gradients band badly — which is most of what generated clips are.

One ffmpeg pass, not N

For a 12-frame filmstrip, tile=12x1 in a single pass takes 97 ms. Pulling twelve frames individually is twelve process spawns and twelve full decodes — a GIF has no keyframe index, so every seek re-decodes from the start.


6. What a real library looks like

Composition of a working 137-item library, which shaped several design decisions:

OriginCount
text → video68
extensions24
mends24
image → video (source still in library)13
image → video (external upload, not stored)7
loop exports1

"Re-run this from the same source frame" is a minority path. Most items have no source frame at all. A feature that promises it universally is lying for the majority — it has to route by origin and disable itself with a reason otherwise.

Most settings were never chosen

116 of the items never recorded a motion value. The server default simply applied. Any UI that shows "Motion: 55" for those is presenting an accident as a decision.

This forced a three-state model rather than the obvious two:

  • set — recorded, differs from the default: the user chose it
  • default — recorded, matches the default
  • assumed — never recorded: the default applied and nobody agreed to it

A default that had never once run

video.js declared baseStrength: 0.95. But the request validator materialised strength: 1 before the builder could ever reach its own fallback — making the 0.95 unreachable dead code. The library proved it: 74 of 82 clips recorded strength 1, and not one recorded 0.95, while the UI slider sat at "0.95" and called it the default.

Blog angle: a default value that has never executed, in a UI that advertises it, is invisible until you count what actually landed on disk.

Payload weight is mostly repetition

/api/lineage for this library: 110,310 bytes. Adding raw generation params per node would add +76% — and models{} alone accounts for 32,674 bytes: six identical filename strings repeated once per node. A whitelist plus a payload-level dedupe table costs +19–27% instead.

Most items are unconnected

68 nodes with 29 apparent origins collapsed to 50 nodes and 11 origins once items with no parent and no children were hidden. Roughly a quarter of a real library is one-offs that bury the lineage the graph exists to show.


7. Bugs worth writing up

A collection of failures that were invisible rather than loud — the interesting category.

Number(null) is 0, and 0 is finite. URLSearchParams.get() returns null for an absent parameter. A naive Number.isFinite guard therefore accepts it and clamps an absent speed to the 0.25 floor — silently returning slow motion for a request that never mentioned speed.

A silent no-op from a lookup that returned the wrong map. Graph node clicks resolved through the raw fetched data, while the DOM was built from the rendered view. Nodes that existed only in the view (in-flight jobs) returned null and the click quietly did nothing.

A deliberate design choice becoming a bug one feature later. In-flight "ghost" nodes deliberately never wrote themselves onto their parent's children array — correct, because a finished job would otherwise leave a dangling id forever. Two features later, chain-selection walked children and silently skipped every running job. The original comment explaining why is what made the second bug findable.

A graph walk that overshoots its own filter. A reachability helper marked ids as visited before checking whether they resolved, so under a filter it returned nodes one hop outside the visible set. In a "file these into a group" feature, that means filing items the user cannot see.

A fast path that fired too eagerly. A stream-copy shortcut triggered on any "repeat once at 1× speed" — including requests that changed container or dropped audio. Asking for WebM would have handed back an untouched MP4.

Audio reintroduced after being declined. A ping-pong filter chain rebuilt its audio branch from "does the source have audio" rather than "did we resolve an audio label", re-adding a track the caller had just asked to drop.

ffmpeg rejects an empty -filter_complex. A plain transcode pushes no filters, so the flag must be omitted entirely rather than passed empty.

An extension guard that corrupted by coercing. An upload path forced any unknown extension to .png — so a GIF was storable, just stored as GIF bytes under a name nothing could decode.

A whitelist that silently dropped new fields. A normaliser copied a fixed list of keys; twice, a new field was added upstream and simply never arrived, with no error anywhere.

A positional index that retargeted itself. A lightbox tracked the open item by list position. When a newly generated item shifted the newest-first list, actions silently applied to a different item — including downloads and "extend this".

Common thread: none of these threw. They returned undefined, 0, null, or the wrong-but-valid thing. The ones that surfaced did so because a number was counted against real data, not because anything crashed.


8. Design positions worth defending

Things that came out of the above and are arguable enough to be interesting.

Measure, don't estimate, when the estimate can be wrong in the direction that matters. Two seconds of waiting beats a confident wrong number someone plans around.

Three states beat two whenever "unset" is possible. Anywhere a default can apply silently, a UI that shows only "changed / unchanged" fabricates intent.

A format conversion is not a new artifact. A loop of a clip is something you can extend again and earns a place in the history graph. A WebM of a clip that already exists is a delivery copy — filing it would add an identical-looking node per platform someone exports for.

Raw material is not the user's work. A pasted GIF stays out of the gallery. Putting someone else's image among a user's own output is a category error.

Frame zero is almost never the interesting frame. Given an animation as input, where you continue from should be the user's decision, not a silent default.

An explicit statement beats a heuristic. When a user has manually filed items into a group, a "hide unconnected items" heuristic must not then hide them. Grey the heuristic out and say why, rather than silently overriding either one.


Reproducing any of this

The app is MIT, zero runtime npm dependencies, no build step: <https://github.com/MassiveNoobie/localVideo>

Requirements: macOS on Apple Silicon (36 GB+ recommended, 64 GB+ for comfortable video), Node.js 22+, a running ComfyUI, and BF16 model weights.

Open →
general Local video generation on Apple Silicon Aug 2, 2026

Why every tool fails on a Mac, and what we did instead

If you own an Apple Silicon Mac with real memory and you've tried to generate video locally, you've hit the same wall everyone does: you download 30 GB of curated weights, hit generate, and get

RuntimeError: Undefined type Float8_e4m3fn

This is a field report on why that happens, why it's not a Mac limitation the way it looks, and what we built once we understood it.


The wall: FP8 is a roach motel on Metal

Nearly every curated model bundle ships FP8 weights. FP8 exists for a good reason — it halves an FP16 model so a 22B video model fits in a 24 GB consumer NVIDIA card. It's the compression that makes local generation possible on gaming hardware.

PyTorch's Metal backend has no float8 kernels. That much is documented. What isn't documented is how completely closed the trap is. We tested every escape:

x = torch.zeros(8, dtype=torch.float8_e4m3fn, device="mps")

x.to(torch.bfloat16)     # RuntimeError: Undefined type Float8_e4m3fn
x.to("cpu")              # RuntimeError: Undefined type Float8_e4m3fn
x.view(torch.uint8)      # RuntimeError: Undefined type Float8_e4m3fn

You can put an FP8 tensor onto the GPU. You cannot convert it, copy it off, or even reinterpret its raw bytes. There is no userspace workaround — not a shim, not a monkeypatch, not a clever cast. We wrote all three before accepting it.

The failure is also path-dependent, which is why the situation looks so confusing from the outside:

PathResult
FP8 image checkpoints that dequantize on the CPU at load✅ works, just slower to load
FP8 video checkpoints that requantize on-device when applying a LoRA❌ hard fail

So FP8 images work and FP8 video dies, on the same machine, in the same app. That inconsistency is why "does this run on a Mac?" gets contradictory answers.

The unlock: stop compressing

Here's the part that reframes everything.

FP8 is a workaround for not having enough memory. A Mac with 64–128 GB of unified memory doesn't have that problem. It can hold the model uncompressed.

The BF16 build of LTX 2.3 22B is a 46 GB file. On a 24 GB NVIDIA card that's a non-starter — hence FP8. On a 128 GB M5 Max, torch.mps.recommended_max_memory() reports 115 GB allocatable. It just… fits.

Requested to load LTXAV
loaded completely; 40053.34 MB loaded, full load: True
8/8 [01:00<00:00, 7.53s/it]

A 22-billion-parameter video model, full precision, resident in a laptop's memory, sampling at 7.4 s/step. No quantization, no shim, no compromise. The BF16 file is a drop-in for the FP8 one — the loader doesn't care.

The weights that don't work on Apple Silicon are the ones built for cards smaller than your Mac.

The second wall: attention doesn't care how much memory you have

Fixing precision got video generating. Then a large request killed the backend:

RuntimeError: Invalid buffer size: 456.35 GiB

The two-stage LTX pipeline refines at 2×, and the refine pass attends over roughly (W/32) · (H/32) · (frames/8 + 1) tokens. That's quadratic in resolution and linear in duration, so 1600 px × 11 s asks for a single 456 GiB allocation. No GPU satisfies that; it just fails later and uglier on CUDA.

Past about 45k tokens the allocation aborts. That's computable before you submit, so the right move is to check and refuse with an explanation, not to crash mid-queue.

Which leads to the most useful feature we built. The refine pass is ~60% of generation time, so making it optional gives you two modes:

2 s clipLong/large clips
Draft (decode stage 1)~87 sfine at any size
Refined (+2× refine)~4 minbounded by the token budget

Draft is ~2.7× faster and handles the 1600 px × 11 s request that crashes in Refined. Iterate in Draft, commit in Refined.

Flags that matter

PYTORCH_ENABLE_MPS_FALLBACK=1 \
PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 \
python main.py --use-split-cross-attention --fp32-vae
  • HIGH_WATERMARK_RATIO=0.0 lifts the allocator cap — required for a 46 GB model.
  • --fp32-vae is the first-line fix for MPS decode NaNs.
  • Never --bf16-vae — it breaks the LTX audio VAE.
  • Never --force-fp16 — black frames on recent macOS.
  • SageAttention and FlashAttention are CUDA-only. torch.compile doesn't work on MPS.

Measured (M5 Max, 128 GB, macOS 26.4, torch 2.13, ComfyUI 0.29)

JobTime
Image 512² · 4 steps~13 s
Image 1024² · 8 steps~45 s
Video 2 s · Draft~87 s
Video 2 s · Refined · 1280²~4 min warm
Video 8 s · Refined · 1280²~24 min
LTX 2.3 22B BF16 sampling7.4 s/step @ 49 frames · 36 s/step @ 201 frames

Published M1/M2/M3 benchmarks are a pessimistic floor for M4+: those chips emulate BF16, while M4 and later do it in hardware.


What we built

localVideo — a local image and video studio designed around these findings instead of fighting them.

  • Picks BF16 weights automatically, and greys out anything your backend can't

run with the actual technical reason

  • Draft / Refined and S / M / L, so speed is a dial rather than a surprise
  • Computes the attention token budget before submitting and tells you the max

duration for your size instead of aborting the backend

  • Offline by default: binds loopback only, zero telemetry, no CDN assets. Pull

the ethernet cable and it still works.

On phoning home

"Local" and "private" get used interchangeably in this space, and they aren't the same thing. A tool can run every model on your GPU and still narrate your session to a server somewhere.

So we audited what actually leaves the machine, and it's worth knowing what's normal in local-generation tools:

CallTriggerTypical default
Product analytics to a hosted endpointapp launch, every generation (with a model label)on, opt-out
Update check against a release APIsign-in, then every few hourson
Model/registry fetchesinstalling weights or browsing a catalogon demand
Font/script CDNsevery page loadinvisible, always

None of that is malicious — analytics answer real product questions, and update checks are a courtesy. But an opt-out beacon that fires on every generation is a strange default for software whose pitch is that your prompts never leave your computer, and a CDN font request means the UI doesn't fully work offline.

localVideo makes exactly zero outbound connections. Not fewer — zero:

  • no analytics, telemetry, crash reporting, or usage beacons; there is no SDK to disable
  • no update checks, no release pings, no license or entitlement calls
  • no CDN fonts, scripts, or styles — every asset is served from your own machine
  • binds 127.0.0.1 (LAN access is an explicit --lan flag, never a default)
  • the only socket it ever opens is to your local ComfyUI

There's no privacy toggle in the settings because there's nothing to toggle. The verification is a one-liner: run it with the network down and nothing changes. Or put it on a firewalled network and watch it stay silent — we did both.

Don't take our word for it — grep the source. Every URL in the codebase:

$ grep -rhoE "https?://[a-zA-Z0-9.:_-]+" server.js lib/ public/ | sort | uniq -c
   5 http://127.0.0.1:8188      # your ComfyUI
   1 http://www.w3.org          # SVG xmlns namespace — an identifier, never fetched

That's the whole list.

On dependencies

We kept exactly one: ComfyUI, as the generation engine. It's genuinely good at being a graph executor, its node API is stable and introspectable, and reimplementing diffusion pipelines to avoid it would be vanity, not engineering.

Everything else is gone:

localVideo
npm packages0 — Node built-ins only, no node_modules
Build stepnone — the frontend is plain HTML/CSS/JS
ComfyUI custom-node packs0 — all 33 node classes we emit are core ComfyUI
Analytics / telemetry SDKs0
Platform installersnone — it's a Node script

That third row is the one we're proudest of. Comparable tools require five or more third-party node packs, each an independent Python project that can fail to build — one of them (numba-based) simply doesn't compile on macOS + Python 3.12, which silently breaks an entire workflow family. Sticking to core nodes means the install is "clone ComfyUI, download weights, run."

Graphs are built against ComfyUI's own /object_info signatures, which is the authoritative description of what each node accepts. That also caught a real bug class: ComfyUI 0.29 types some inputs as unions ("FLOAT,INT"), and a builder that only recognizes single scalar types silently drops them — producing graphs the server rejects with a confusing required_input_missing.

A native MLX backend that removes the ComfyUI dependency entirely is interesting and plausible on this hardware. It is not what this is today.

Credit

Mix Studio by BlackMixture is what convinced us a local generation front end could feel genuinely good rather than like a node graph with a hat on. It's Windows/NVIDIA-only and doesn't run this hardware, so localVideo is an independent implementation written from ComfyUI's public API — no shared code — but the inspiration is real and worth naming.

Thanks also to the ComfyUI team, Lightricks (LTX-2.3), and Krea, whose models and published reference workflows made the rest possible.

Open →
general What building the (code editor) roadmap taught us that writing it couldn't Aug 2, 2026

A follow-up to Cline lessons learned + feature roadmap for Yee easy mode.

Last time I published a competitive study of Cline, eighteen lessons, and a ranked backlog: 26 features, broken into 81 steps, each scored for complexity, sequenced across five milestones. It ended with "research complete — roadmap awaiting a ranking pass."

We did the ranking pass. Then we built it.

Where it stands: 51 of 81 rows shipped, 111 of 188 complexity points. Every row through the same gate — a hermetic test guard, the full chain green, an adversarial review, and a commit that says what it did. The test suite is now 74 steps and 2,510 assertions and costs nothing to run: no model call, no network, so it runs on every change.

That's the boring part. The interesting part is that executing a roadmap teaches you things writing one cannot, and three of those things were uncomfortable enough to be worth a post.


L19 — The roadmap was wrong in one specific, dangerous place

Row CL-04 was "classified command allowlist." The idea: instead of approving npm test over and over, approve the class of command once and stop being asked. Cline does something like this. It's an obvious quality-of-life win. I ranked it P1 and specced it in four steps.

Before writing any code, we ran three independent threat models against the design and had them judged.

The verdict: a grant keyed on a command's class is standing code execution.

The reasoning is simple once someone says it out loud. A class name doesn't bind to behavior. npm test doesn't mean "run tests" — it means "run whatever scripts.test currently says," and the agent editing your code can edit that. Grant the class, and you've granted anything that file can be made to say.

The obvious fix — hash the config files and re-ask when they change — doesn't hold either. scripts.test = node scripts/test.js moves the payload one level deeper than the hash can see. Same for tests/conftest.py, node_modules/.bin/*, and .git/config textconv drivers.

Three holes got executed against candidate designs rather than argued about:

  • find . -exec inside a class called "inspect"
  • git branch -D main inside a class the interface labelled read-only
  • npx <tool>, which fetches and runs a package from a mutable registry

And the counterweight that killed the remaining case for it: the strict shell grammar you'd need to make classes safe refuses npx vitest run — this repo's own idiom. So the prompt-reduction payoff was small and the new attack surface was large.

What shipped instead: grants key on the exact command bytes you read in the modal, scoped to one project, surviving restarts. That inherits the session allowlist's safety property by construction — the granted string is the string that runs — while still killing the re-prompt-every-reload loop that trains people to click through without reading.

The lesson isn't "we caught a bug." It's that a roadmap row is a hypothesis, and some of them are wrong in ways only the build reveals. The row now carries a deviation note explaining what shipped instead and why, sitting next to the original spec. A backlog that only records intentions is a worse document than one that records collisions with reality.


L20 — A test suite can be green while the product lies

Here's a dialog Yee shipped:

No standing approval for this one — it uses "pnpm @dev3lop/site", which can change more than it reports. [ Deny ] [ Always allow this command ] [ Always allow here ] [ Run once ]

Read it twice. It says there is no standing approval available, directly beside an enabled button offering exactly that.

Every logic test passed. They were all correct. The button's condition was derived from one property (destructive) while the explanation was derived from another (grantable), and nothing in the suite had ever rendered the two together. A unit test can verify each half and never notice the pair contradicts itself, because the contradiction only exists on screen.

There was a second bug in the same screenshot: pnpm --filter @dev3lop/site build was refused because --filter consumes the next token, and its value was being read as the subcommand. Hence the nonsense message naming a package as if it were a command.

So we built a click-through harness. bench/app-drive.sh boots the real app hidden and evaluates scripts inside the real renderer — importing the shipped modules, opening the real modal, clicking the actual buttons, and asserting what a user would see. Zero tokens, and it runs in the chain like everything else.

87 clicks across five drivers now. The push modal, the approval modal, the endpoint settings, the secrets boundary through real IPC, the chat pills.

It earned itself on the first run by catching an unrelated regression: a refactor had collapsed rm -rf ./build into a generic "deletes files," losing the recursive-tree warning the modal used to give. No logic test would ever flag that — the string was still a valid reason.

If your interface makes claims, something has to read them.


L21 — Three of my own tests proved nothing, and mutation testing is how I know

This is the part I'd rather not write.

The discipline we adopted early: after writing a guard, seed the exact fault it claims to catch and confirm it goes red by name. Not "the suite fails" — that specific assertion, with its specific message.

It caught my own work three separate times.

The vacuous fixture. I wrote an assertion that the push modal names the branch it actually cuts from — the exact bug that had shipped and burned us. My test fixture set startBranch and defaultBranch both to main. So the assertion matched "off main" whether the code named the cut base or the branch you happened to be standing on. I reinstated the original bug and the test stayed green. It now reads the summary line with two different names.

The substring pin. I added a check that capturing the preview appends rather than replaces. It asserted a push(...) call existed in the source. I then seeded a fault that cleared the previous captures immediately before pushing — the substring still matched. Green. It reads the whole function body now.

The crash that looked like a pass. A seeded fault made a test crash mid-run. It printed no summary line, so grep FAILED found nothing and I read it as passing. Content assertions now short-circuit on error results so a mutation dies as a named red, not a silent stack trace.

Every one of those tests looked right. Two of them I'd have shipped and cited as proof.

A test that cannot fail is worse than no test, because it is counted as evidence.

The related habit that comes with it: vacuous-pass armor. Every guard sets process.exitCode = 1 and only a completed summary line flips it, plus a watchdog. Otherwise an async stall drains the event loop and exits 0 — reading as green. Suites also pin their own assertion count, so quietly losing a case is a failure rather than a smaller number nobody reads.


L22 — A number computed over lossy data is worse than no number

This one turned up three separate times in three unrelated features, which is how you know it's a real pattern and not an anecdote.

Phantom pricing. We added support for any OpenAI-compatible endpoint — OpenRouter, Groq, Together, LM Studio. The price resolver fell through to a default of $3/$15 per million tokens for anything it didn't recognise. Which meant a call to your own local server, costing nothing, would have been billed at Claude-tier rates into the spend meter, the receipts, and the generated PR body. We cannot know what a third party charges. So it's your number or an explicit unknown. Never a guess.

Silent truncation. The per-chat activity trace was capped at 400 entries with slice(-400) — which silently discards the earliest rows. Fine for a scrolling log. Fatal the moment you build an apply-success meter on it, because the rate gets computed over a biased tail and states a confidence it hasn't earned. The cap now leaves a marker recording what it dropped, and any statistic built on it reports itself as partial.

Discarded failures. Edits were only captured for training when they succeeded — the call site literally gated on success. Half the signal was going in the bin, and it was the more useful half: the instruction, the file, and the search block the model invented that matched nothing. Now failures are captured too, labelled negative at the schema level — because the row builder has a fixed field list, and a label set by the caller would have been silently dropped, putting a wrong edit into the training warehouse looking exactly like a win.

Same rule underneath all three: when you don't know, say so. An honest gap beats a confident fabrication, and it is much cheaper to fix later.


L23 — Stop treating the backlog as a queue

Halfway through, the sequencing stopped making sense.

M1 and M2 earned their place. They fixed spots where Yee was genuinely worse than table stakes — commands silently denied under concurrency, loops dead-ending with no way to continue, repo rules ignored, no way to restore a single turn. That work needed doing regardless of what Cline shipped.

Past that line, running the remaining rows in order converts into copying a better-funded team on their own axis. That's a race you lose by construction: you're always behind, by definition, and every hour spent matching them is an hour not spent on the things they can't answer.

So the grid became a menu. What gets built next is picked on one question: does this fix something that harms a real user, or widen something a competitor cannot answer?

Five rows survived that test, and they were not the next five in rank order:

why it jumped the queue
.yee/ignore boundary.env and keys were readable by the agent and harvestable into training data. A launch blocker, not a gap.
Image attachPaste a mockup or a bug screenshot. Cheapest real win on the board.
Non-web verificationA hardcoded npm fired false failures in pnpm repos, triggering paid fix turns against code that was fine.
compat: providerOne row opens OpenRouter, Groq, LM Studio, Together — the routing already existed.
Apply meter + negativesFeeds the training pipeline real per-model data instead of a synthetic sweep.

Roughly the same complexity budget as one milestone. Far more value.


L24 — We are not building MCP, on purpose

Every agentic editor ships MCP. We held it, and I want to be precise about why, because "we didn't get to it" and "we decided not to" are different statements and only one of them is honest.

The case for is real, and includes a correction to my own reasoning. I argued for the compat-provider row because "one integration opens OpenRouter, Groq, LM Studio." MCP is the same shape at larger scale — one integration inherits hundreds of tool servers. I used that argument in one direction and not the other, and the difference is cost, not principle.

What's genuinely different is a cost specific to us. Yee's measured advantage is that its ecosystem — preview-awareness, redundant-command interception, a directness directive — makes frontier models spend fewer and steadier tokens for the same result. We have numbers on that. The standard MCP integration mirrors every connected server's tools into the turn-1 vocabulary and freezes it for cache stability, which means every chat pays context for every tool of every connected server, used or not. Connect three servers with a dozen tools each and you have quietly widened the exact thing you measured as your edge.

Add the rest: it's a permanent maintenance surface, and an MCP server is arbitrary code with tool access — running outside the secrets boundary we just spent a week building.

Nobody has ever chosen a code editor because it had MCP. They choose one because it does the thing they came for without lying to them.

It comes off hold the day a real user asks for a specific server by name — and when it does, the shape won't be "MCP support." It'll be the two or three servers that earn their context, enabled per-chat so a conversation that doesn't need a database never pays for one.

The backlog records this as held (⏸), deliberately distinct from not started (☐). A reader has to be able to tell an unmade decision from a made one.


L25 — Documentation rots in a specific, corrosive way

Catching the READMEs up after three weeks, I found the roadmap section listing three things as "later" that had already shipped: inline edit, @file mentions, signed macOS builds.

That's worse than merely out of date. There's an "honest list" in that README — the things Yee genuinely cannot do — and it's the most valuable section in the file. If a reader spots one item they know shipped, they stop trusting the honest list too. Stale optimism poisons stated limitations.

The honest list gained three entries in the same pass rather than shrinking: no MCP (held, with reasons), a standing approval pins the command and not its meaning, and Windows/Linux are unbuilt.

The same rot hit our own diagrams, and worse. We keep two: a system map of what the app is made of, and a test map of what protects it. Both were stale — and the test map's staleness was a bug in the tool that generates it. It cached a chain run with no way to tell the chain had changed, so every regeneration quietly re-described the previous suite. It claimed 73 steps and 34 clicks against a real 74 and 87.

That is L22 — a confident number over data that moved — living inside the tool that draws the picture of our tests. The cache is keyed to the test script now. And the system map's node count, which had been hand-typed and wrong for a week, is derived from the arrays.


Where this leaves the thesis

The first post argued that Cline's 8M developers came from trust ramps, context hygiene, and extensibility, and that Yee should learn from all three.

Building it, the trust ramps mattered most — and not because of the feature list. They mattered because every one of these lessons is the same lesson in a different costume: the system stated something that wasn't true. A modal that contradicted itself. A test that couldn't fail. A price that was invented. A trace that dropped its own history. A roadmap that listed shipped work as pending.

None of those are hard problems. They're all cheap to fix once seen. The expensive part is seeing them, and the only reliable way we found is to make the software prove its claims to something that doesn't take its word for it — a mutation, a click, a marker recording what got dropped.

That's what the remaining 30 rows are being judged against now. Not "does Cline have it."


Yee is a local-first AI code editor. The 81-row grid, the 74-step test chain, and both maps live in the repo — the test map is a standalone page you can open with no server: every step, what it protects in plain English, and the incident that caused it to exist.

Open →
general Cline Lessons Learned + Feature Roadmap for Yee Easy Mode Jul 30, 2026

Date: 2026-07-29 Source: cline.bot + docs.cline.bot + github.com/cline/cline (CHANGELOG through v4.0) + 2025-2026 reviews/HN — verified by a 10-agent orchestrated sweep (3 Cline researchers, 3 Yee code readers, 3 gap-analysis lenses, 1 completeness critic). All Yee claims below are grounded in code actually read this session (file:line cited). Status: ✅ Research complete — roadmap awaiting Ty's ranking pass Standing constraints honored: ⛔ no paid runs anywhere in this plan (every metric proposed comes from users' organic usage on their own keys) · no tab-completion · build ON existing modules, never rebuild · byte-stable system prompt (cache invariant) is inviolable · safety net sweeps everything, always.


TL;DR

Cline won 8M+ developers with three moves we should study: (1) trust ramps — Plan/Act hard-blocked modes, per-category approvals, checkpoints-in-the-chat — that let users dial autonomy up gradually; (2) context hygiene as first-class UX — Focus Chain, visible auto-compact, /newtask handoffs — their answer to long-run drift; (3) repo-resident extensibility — .clinerules, workflows, MCP — config that rides in git and spreads team-to-team. Their #1 documented churn driver is cost blowup despite transparency ($50–200/day team runs; the spend-limit UI came embarrassingly late, v3.78) — which is exactly the surface where Yee is structurally ahead (cache-aware pricing, 150k/300k budget gates, $0 local compaction).

Where Yee is ahead (double down, market it): the shadow-git DAG with real merge/rebase/safety-net (Cline's shadow git is branch-per-task restore; it's disabled entirely in multi-root workspaces), preview-anchored verification (error scrape → fix loop → Lighthouse → pre-push deploy fact-check), cost honesty with gates not just meters, and the farm→forge→split-test measured-tuning loop Cline has no equivalent of.

Where Cline is ahead (the gaps to close): recovery lives in a separate canvas instead of inline in the chat; our loop dead-ends silently at the iteration cap; compaction/degradation is invisible; easy mode has no read-only mode and silently overrides the user's auto-apply preference; approval grants are exact-string and session-only (and concurrent approvals race — a real bug); nothing we read rides in the user's repo (no rules files, no workflows); the model picker is a hardcoded 16-model list despite the OpenAI-compat plumbing existing; and there's no MCP story at all.


1. What Cline is (verified inventory, compressed)

Positioning (their words): "transparency over convenience, developer control over vendor lock-in, full model power over margin optimization, open source over proprietary black boxes." Apache 2.0, BYO-key, 40+ providers incl. local (Ollama/LM Studio) and OAuth piggybacking on ChatGPT/Claude Code subscriptions. $32M raised July 2025; enterprise tier Oct 2025; ClinePass ($9.99/mo open-weight models) June 2026.

Core loop features (all shipped, VS Code + CLI):

  • Plan & Act modes — Plan is hard-blocked from edits/commands (structural, not behavioral); history carries across the flip; optional per-mode model pairing. The single most user-cited trust feature; Cursor and Copilot copied it.
  • Checkpoints via shadow git — commit after every tool op; bookmark in the chat with Compare + Restore; three restore scopes (Restore Files / Restore Task Only / Restore Files & Task — i.e. they can rewind the conversation too, which we can't). Branch-per-task since v3.6. Disabled in multi-root workspaces; slows on huge repos.
  • Auto-approve + YOLO — per-category toggles (reads/edits/safe-commands/all-commands/browser/MCP), model-assigned requires_approval per command, OS notifications when blocked ≥30s. YOLO = everything approved (v3.31, enterprise-disableable).
  • Focus Chain (v3.25, default-on) — auto-maintained markdown checklist re-injected every 6 messages, n/m progress in the task header. Their fix for long-horizon drift.
  • Auto Compact (v3.25) — visible summarization tool-call with its cost shown near the context ceiling; manual /smol; documented fallback to rule-based truncation. Their answer to "5M-token task in a 200k window."
  • Slash commands — /newtask (package progress+plan+files into a fresh clean-context task), /deep-planning (silent explore → questions → implementation_plan.md → spawn fresh implementation task), /newrule, /smol, /reportbug.
  • Diff editing — replace_in_file with order-invariant apply, continuously tuned against a public evals/diff-edits harness; they credit >10% gains to treating diffEditSuccess as the loop's core health metric. v4.0 moved toward apply_patch/editor tools.
  • Loop detection + auto-retry with backoff (v3.76) and an experimental completion double-check pass (v3.58): every hard limit has a continuation ramp.

Extensibility (the ecosystem layer):

  • .clinerules/ — repo-resident, version-controlled, team-shared rules; per-file toggle UI; conditional path-glob frontmatter; auto-detects .cursorrules / .windsurfrules / AGENTS.md (competitors' installed base = their onboarding funnel).
  • Workflows — markdown files → /name slash commands, injected once as explicit_instructions (no persistent prompt tax); "generate a workflow from what we just did."
  • Skills (v3.48) — progressive disclosure: ~100-token metadata always, <5k loaded on activation.
  • Hooks (v3.36) — PreToolUse/PostToolUse scripts, fail_open/fail_closed. Notably: .clineignore is being deprecated because advisory filters got bypassed (mentions, shell) — replaced by hook-enforced runtime blocking. Enforce at the boundary or don't bother.
  • MCP — STDIO servers, per-tool autoApprove arrays, in-product marketplace (Feb 2025), routed through the same approval system as built-ins.
  • Subagents — read-only research fan-out (use_subagents) keeping the main context clean, per-subagent cost rolled into the task total.

Breadth we are explicitly NOT chasing (see §5): CLI/TUI, Kanban (parallel agents on worktrees), Agent Teams, Slack/Discord/Linear connectors, cron scheduling, enterprise SSO/governance, dictation, marketplace.

Their documented weaknesses (as valuable as the features):

  1. Cost blowup — the persistent 2024-2026 complaint: $52–187/mo typical, "$50-200/day in team settings," 9× a Cursor seat; spend-limit UI only in v3.78 (Apr 2026). Transparency ≠ affordability. Gates matter.
  2. Context regressions — the no-throttle philosophy produced token churn / context-loss regressions (issue #5616; users restarting every ~10 messages). Our cache-stability discipline is the countermeasure they lack.
  3. No autocomplete + rougher polish — structural, acknowledged in most 2026 reviews. (Irrelevant to us: tab-completion is explicitly not our priority.)
  4. Forkability — Roo Code forked them, iterated faster, siphoned power users through 2025 (archived May 2026). The durable moat was the eval-tuned harness + steadiness, not any copyable feature. Our equivalent moat is the measured substrate (farm→forge→split-test on the user's own keys) — any sharing feature must carry that discipline, never bypass it.

2. Lessons learned

Where Yee is AHEAD — double down, don't dilute

  • L1. The checkpoint DAG is a marketable differentiator. Cline's whole autonomy thesis rests on "checkpoints drop the cost of a mistake to near zero" — and our engine is stronger where it counts: real merge/rebase, safety-net commits that sweep everything (incl. gitignored/stranger files), ignored-clobber guards, authored-only push staging ([lib/checkpoints.js](../building_block/editor/lib/checkpoints.js), [app/branchGraph.js](../building_block/editor/app/branchGraph.js), [app/pushSelect.js](../building_block/editor/app/pushSelect.js)). Cline's checkpoints are disabled in multi-root workspaces — an easy honest marketing line for us. One caveat where they lead: their three restore scopes include conversation rewind; ours is files-only (→ CL-01).
  • L2. Cost honesty with GATES is the brand working. Cline proves per-loop cost display alone doesn't retain users — the churn came anyway, and the gate came late. Our cache-aware per-token-type pricing, ⚡cache-hit%, 150k soft / 300k hard per-turn pause with keep-going/stop ([app/synthwave.js](../building_block/editor/app/synthwave.js) BUDGET/budgetGate), and $0 local-mlx compaction are the preemptive version of their retrofit. Rule going forward: every new surface gets metered and gated on day one (MCP calls, compat endpoints, workflows) — and when pricing is unknown, show tokens + an explicit "$ unknown," never a fabricated figure.
  • L3. Preview-anchored verification is our lane. Cline verifies via terminal watching + a heavyweight browser tool + an experimental double-check pass; our chain (noise-filtered preview error scrape → fix-in-chat through the same synthSend path → reverify → real Lighthouse pills → two-tier pre-push deploy fact-check that caught a real production failure) verifies at the surface the user ships. Nothing in their inventory has the pre-push deploy fact-check. But the critic's catch stands: this lane currently only covers web repos (→ CL-11).
  • L4. Byte-stable-system-prompt discipline is a moat. Cline's context-regression record is what happens without it. Our invariant — EASY_QUALITY_PROMPT byte-identical, volatile context rides user turns, repo map pinned in the turn-1 pair surviving eviction, <30% cache-read tripwire — is the stricter, more honest version of their rules/skills tiering. Every feature in §4 injects via the turn-1 rider or follow-up prompts, never the system prompt. That's the design contract.
  • L5. The measured substrate is the unforkable part. Promotion-requires-measured-score (on the user's own keys) is the discipline Cline's marketplace lacks and the reason a fork can't take the moat. Shared/imported anything arrives unmeasured and only earns default status through the user's own measured runs.

What Cline teaches us — the gaps

  • L6. Never dead-end silently; every hard limit needs a visible exit ramp. Cline pairs every ceiling with a continuation (auto-compact, retry-with-backoff, loop detection, /newtask). Our runAgentLoop exhausts MAX_ITERATIONS=10 and just… returns — possibly with an unexecuted `tool block rendered as prose — even though we already built exactly the right UX for the spend limit. (→ CL-02)
  • L7. Recovery must live where the panic happens — inline in the chat. Cline puts Compare/Restore bookmarks on every tool op in the conversation. Our stronger engine hides behind a separate canvas and a small 🛟 toggle — and our own 2026-07-18 "unrecoverable files" scare was explicitly a discoverability failure while the data sat safe in the shadow repo. An undo engine users can't find at panic-moment is functionally no undo engine. (→ CL-01)
  • L8. Show the machinery: compaction, degradation, and dial-turns are trust surfaces. Their auto-compact is a visible tool call with a cost; our _capTurnsCompacted distills invisibly and — when local mlx is down — silently drops the conversation middle with no note. Also: easy mode force-calls setAutoApplyEdits(true) over the user's persisted preference with zero disclosure. The honest-instrument posture must cover internal state, not just dollars. (→ CL-06, CL-05)
  • L9. A hard-blocked read-only mode is an onboarding ramp, not a power-user nicety. Plan/Act is Cline's most user-cited trust feature because the block is structural. We already have persisted edit/plan/auto behavior modes as "the canonical source of truth" ([app/agentLoop.js:1966](../building_block/editor/app/agentLoop.js)) — but they're main-editor only; the flagship easy-mode surface always runs full-act, force-auto-apply. First-run experience = maximum autonomy, no ramp. (→ CL-05)
  • L10. Approval policy should key on what a command IS, not its exact bytes — and concurrent approvals must queue, never supersede. Their model-assigned requires_approval + persistent per-category toggles vs. our exact-string, session-only allowlist that re-prompts on one-char variation (training users to click through). Worse, requestCommandApproval denies a pending modal when a new one arrives — at PLAN_CONCURRENCY=3 an orchestrated step's command can be silently denied. That's a bug, not a preference. (→ CL-03, CL-04)
  • L11. Long-horizon reliability = a persisted north-star artifact re-injected on a cadence. Focus Chain, mechanically: checklist, re-inject every 6 messages, n/m in the header. We have the artifact machinery (agentPlan checklists, steeringQueue's proven single-drain-point injection) and re-inject no task artifact (the repo map's MAP_REFRESH_EDITS drift refresh is the only rider refresh). (→ CL-08)
  • L12. Context handoff is a first-class loop operation. /newtask forks the task state, not just the tree state. Our fork moves the workspace to a sha but the new chat inherits only the repo map — the accumulated understanding, which our local distiller could package for $0, is discarded exactly when it's most valuable. (→ CL-09)
  • L13. Repo-resident config is the unit of ecosystem propagation. Rules/workflows that ride in git spread team-to-team; honoring competitors' formats (.cursorrules, AGENTS.md, CLAUDE.md) converts their installed base into your onboarding funnel free. Everything we read today lives in SQLite/localStorage — personal and machine-local; repos already carrying AGENTS.md are silently ignored. (→ CL-07, CL-13)
  • L14. Extension tools must enter through the one choke point. Our safety-and-honesty spine (approvals, s.activity trace → PR bodies, farm capture, addTok metering) keys on the fixed tool vocabulary dispatched in executeTool. Any tool path that bypasses it forks the safety story and starves the meter/farm/PR surfaces. That's the MCP integration contract. (→ CL-16)
  • L15. Enforce exclusion at the tool-call boundary, fail closed. Cline is deprecating .clineignore because advisory filters were bypassed. We currently have no exclusion mechanism at all — every repo file is readable, mappable, and farmable into the SFT warehouse, .env included. We get to skip their advisory mistake entirely. Scoping rule: ignore governs model access + training capture, never the safety net's recovery sweep. (→ CL-17)
  • L16. Edit-apply success is the loop's core health metric — and its failures are free training data. They publish an evals/diff-edits harness and credit >10% gains to it. We already persist the per-tool-call ok bit in s.activity and farm only successful edits — the failure half of exactly this signal is discarded today, and it costs zero paid runs to keep (it's organic usage). (→ CL-12)
  • L17. Provider breadth is the no-lock-in adoption wedge, and most of it is one generic OpenAI-compatible endpoint. (Not all — their subscription-OAuth piggybacking is a separate mechanism a compat endpoint can't reach; that stays a watch item.) Our mlx: path already speaks OpenAI-compat HTTP to a configurable URL; the lockout is just the hardcoded 16-model picker and pricing table. (→ CL-14)
  • L18. Community rituals become product. Memory Bank is a docs-canonized pattern where the frontier model re-reads six markdown files every task — a recurring token tax. We can ship the same continuity as a near-zero-cost built-in because we already persist the raw material (sessions, s.activity, s.authored, tok, DAG) and own a local distiller — PR-body synthesis already proved zero-token composition works. (→ CL-22)

3. The backlog, ranked by need

Legend: P0 = trust/correctness gap users hit now · P1 = high leverage next · P2 = valuable, after P1 · P3 = someday/watchlist. Effort S/M/L/XL. Every item builds on named existing modules; none needs a paid run; all injection is cache-safe (rider/user-prompt only).

IDItemPEffortBuilds on
CL-01Inline turn-restore chips in chat (+ restore scopes)P0Mcheckpoints.js restoreTo, chatStore DAG, branchGraph diff cards
CL-02Iteration-cap continue gate + loop detectorP0SbudgetGate pattern, parseToolCalls
CL-03Approval FIFO queue (bug fix — supersede-deny race)P0Sapprovals.js requestCommandApproval
CL-07Repo rules rider: .yee/rules.md + AGENTS.md/CLAUDE.md/.cursorrulesP0Sturn-1 pinned rider, MAP_REFRESH_EDITS
CL-04Persistent classified command allowlistP1MclassifyCommandPurpose, chatStore
CL-05Plan/Act toggle in easy mode + auto-apply disclosureP1MbehaviorMode (agentLoop.js:1966), executeTool gate
CL-06Visible compaction notes + manual compact + context meterP1M_capTurnsCompacted, normUsage channel, system-note pattern
CL-08Task ledger (focus-chain-lite)P1MagentPlan.js, steeringQueue drain point
CL-09Fork-with-context handoff (/newtask on the DAG)P1MbranchGraph fork, local distiller, s.authored
CL-10OS notifications (approval-blocked / gate-paused / turn-done)P1Sapproval + gate + finally lifecycle seams, Electron Notification
CL-11Easy-mode verification for non-web reposP1MprojectVerify.js, lintCheck.js, turn lifecycle
CL-12Edit-apply success meter + farm negativesP1Ms.activity, farm.js, pipeline popup
CL-13/workflows: repo + global markdown slash commandsP1SsynthSend forced-param dispatch (4 internal precedents)
CL-14Generic OpenAI-compat provider (compat: prefix)P1MllmProvider mlx path, safeStorage, MODEL_PRICING honest-unknown
CL-15Renderer↔main version handshake ("needs restart" banner)P1Spreload/main IPC (precedes CL-16/18/21)
CL-16MCP client v1 (STDIO, text-vocab path, approval-gated)P2LexecuteTool choke point, approvals, devServer.js patterns
CL-17.yee/ignore enforced at the choke point + farmP2MexecuteToolDirect, repoMapFor, captureEdit
CL-18Resume interrupted turn after reload/crashP2Ms.activity tail, baseline checkpoint dirtyStatus, synthSend
CL-19Per-turn receipt lineP2Sper-turn counters, buildPrBody stat line
CL-20fetch_web tool (HTML→markdown, gated + metered)P2MexecuteTool vocabulary, compactResult
CL-21Multimodal attach (drag-drop image → chat)P2Mscreenshot→chat vision path (9baa908), token-estimate chip
CL-22Portable .yeepack bundles (imported = unmeasured)P2Mstreams table lineage, effectiveChatConfig promotion rule
CL-23Zero-cost repo memory briefP3Msessions/activity/authored/tok, local distiller, PR-body precedent
CL-24Read-only research subagentsP3LagentLoop read tools, orchestrator patterns
CL-25Skills-style progressive disclosure (tier-3 instructions)P3Mrider framework from CL-07
CL-26Pre/post-tool hooks (general mechanism)P3MexecuteTool choke point (CL-17 is the concrete instance first)

P0 — close the trust gaps now

CL-01 · Inline turn-restore chips in the chat stream — Every assistant message whose turn produced edits gets a hover affordance: "⎌ restore to before this turn" + "view changes." Restore calls the existing restoreTo (already safety-commits live state, re-places strangers, guards ignored clobbers) against the turn's baseline sha the DAG already stores; confirmation is the existing swap system-note with one-click undo; "view changes" reuses the branch-graph node's lazy diff card. The canvas stays the power surface; this puts the panic path where the panic happens (L7 — and the 2026-07-18 scare is the receipt). Restore scopes (critic catch, and the one axis Cline leads on): after a files-only restore the conversation still "remembers" edits that no longer exist — v1 must at minimum stamp an honest system note into the transcript ("workspace restored to before turn N; edits after that point are no longer on disk") that also rides the model context; a full Cline-style "rewind conversation too" is a fast-follow once the note pattern proves out. Acceptance: click restore on a mid-chat turn → files revert, safety node appears in the canvas, note visible to user AND model, undo works — verified in the live app, not just tests.

CL-02 · Iteration-cap continue gate + loop detector — When MAX_ITERATIONS exhausts with apparent unfinished work (trailing unexecuted `tool block, or edit-intent detected but unapplied), pause with the exact budgetGate UX: "hit the 10-round safety cap" + keep-going (buys another leg) / stop. Render any unexecuted trailing tool block as an honest "planned but not executed" block, never prose. Companion (critic catch): a repeated-tool-call loop detector (same tool + args N times, or no-progress heuristic) — without it, "keep going" can repeatedly fund an infinite loop; with it, the gate message says "the agent appears to be repeating itself" so the user's choice is informed. (L6) Acceptance: a deliberately over-long task pauses with the gate instead of ending silently; a synthetic repeat-loop trips the detector message.

CL-03 · Approval FIFO queue (straight bug fix) — requestCommandApproval currently denies a pending modal when a new request arrives; orchestrated plan steps at PLAN_CONCURRENCY=3 can race and silently lose commands. Queue FIFO. Ship independently of CL-04 — it's a correctness fix, not a feature. (L10)

CL-07 · Repo rules rider — At chat creation, discover rules at repo root in priority order .yee/rules.md → AGENTS.md → CLAUDE.md → .cursorrules → .clinerules/.md, concatenate with per-file caps, inject as a labeled section of the turn-1 user prompt beside the repo map (pinned pair survives eviction; refresh on the existing MAP_REFRESH_EDITS drift path when the file hash changes). Header pill names which file loaded, with a toggle-off — the user sees exactly what rides the prompt. Never touches the system prompt (L4). Honoring the competitor formats converts Cursor/Claude-Code-convention repos into instant-onboarding repos (L13). Verified: zero rules-file support exists today. Also builds the shared repo-config loader that CL-13/16/17/22 reuse. Acceptance:* drop an AGENTS.md into a repo → pill appears, instruction observably honored in output, cache-hit% unchanged across turns (the tripwire is the regression test).

P1 — control, continuity, and the first open doors

CL-04 · Persistent classified command allowlist — Third approval tier: "always allow <class> in this repo," keyed on the existing classifyCommandPurpose class + normalized command family, persisted per-repo in chatStore. Canonical constraints (the critic forced this to be explicit): the gateway evaluateTerminalSecurity blocklist always wins; destructive classes (delete, install, git-mutating) are never offered for standing approval; the tool block shows which tier auto-approved ("auto-approved: run-tests allowlist") so grants stay auditable; a header chip lists/revokes grants. Exact-string session tier stays. (L10)

CL-05 · Plan/Act toggle in easy mode + honest Act disclosure — Surface the already-persisted behavior modes as a two-state header switch. Plan: executeTool hard-blocks EDIT_TOOLS + run_terminal with a structured "planning mode — read and propose" tool result (the existing rejection-as-tool-result pattern, so the model adapts instead of erroring). Act: current behavior, but with the missing disclosure line: "Act auto-applies edits — every turn is checkpointed and restorable" — fixing the silent setAutoApplyEdits(true) override (L8, L9). Optional fast-follow: per-mode model memory via the existing per-conversation picker (cheap model for planning, user's choice, user's key). Acceptance: in Plan, zero file mutations across a full multi-iteration turn (checkpoint diff is empty); flip to Act mid-conversation carries history; disclosure visible on first Act turn.

CL-06 · Visible compaction + manual compact + context meter — (a) _capTurnsCompacted emits over the existing usage-normalization channel; renders a one-line system note: "condensed 12 older turns into a summary (local model · $0)." (b) When mlx is down and plain eviction drops the middle: say so — "older turns dropped without summary — local compactor offline." (c) "Compact now" affordance. (d) Small context-fill pill beside the token meter (last input tokens vs model window, from a per-model window table next to MODEL_PRICING — same honest-rough posture, and note the shared maintenance debt: neither table has a reconciliation mechanism; keep them adjacent so one update pass covers both). Extends honest-instrument from dollars to context state (L8). Notes ride the chat surface, never the prompt.

CL-08 · Task ledger (focus-chain-lite) — Turn-1 drafts a ≤10-item checklist for multi-step asks; the loop appends it compactly to follow-up prompts every Nth iteration via the steeringQueue's proven single-drain-point; model checks items off; header shows n/m. Persisted with the session so it survives eviction and restarts; rides follow-up prompts only (L4, L11). Reuses the agentPlan checklist schema — this is prompt mechanics, not new machinery.

CL-09 · Fork-with-context handoff — "Hand off to fresh chat" node action on the branch graph: distill the conversation via the local compactor ($0) into task-state bullets, carry s.authored + repo map + ledger, run the existing fork flow with the new session pre-seeded. The handoff is itself a DAG node, so lineage stays visible. mlx-down degrades to extractive bullets with an honest note. (L12)

CL-10 · OS notifications — Electron Notification (window-unfocused only): approval modal opened (loop is blocked on you), budget/iteration gate paused (spend frozen), long turn finished, Lighthouse queue done/stalled. Click focuses the window; one quiet-mode toggle. Today a user who tabs away leaves the agent frozen at a modal. (Cline fires these at approval-needed and 30s-running marks.) The fix-queue trigger ships only after the queue's own end-to-end click-through — it's marked untested on the system map; don't build notification UX on an unverified sequencer.

CL-11 · Easy-mode verification for non-web repos — The critic's best catch: our verification moat (L3) covers only running web apps; for CLIs/libraries/Python repos, easy mode verifies nothing after an edit. Bring projectVerify/lintCheck into the easy-mode turn lifecycle: after the turn's edits, run the repo's detected check (typecheck/test/lint) and surface pass/fail as a chip; failures offer the same fix-in-chat path as preview errors. Do the debt fixes on the way in — they directly cause wasted paid fix-turns: package-manager detection (npm hardcoded breaks pnpm/yarn), and lintCheck's new Function() false syntax errors on any import/export (most modern source). Scope v1 to JS/TS + Python detection; cargo/gradle later. Acceptance: in a pnpm TS repo with no dev server, an easy-mode edit turn ends with a real typecheck verdict chip; no false syntax errors on ESM files.

CL-12 · Edit-apply success meter + farm negatives — Aggregate persisted s.activity {t,p,ok} into per-model, per-edit-tool apply-success rates with always-visible n counts in the under-the-hood pipeline popup (upgrading the n=2-12 MODEL_BEST strings toward defensible numbers as organic n grows). Capture FAILED search_replace/multi_edit attempts as negative rows for the forge's negative-weighting pipeline — the failure half of the signal is discarded today (L16). Zero paid runs by construction. Prerequisites (critic): fix s.activity honesty first — the 400-entry cap and 80-char path truncation currently lose early activity with no "and N more" marker, which would silently understate this meter, the receipt line, and PR bodies. Also label truncation-induced mismatches (model edited text it never fully read due to 8000-char read caps) so tool-caused failures don't pollute model-attributed negatives.

CL-13 · /workflows — Markdown files in .yee/workflows/ (repo) + a userData global folder become /name commands with autocomplete in the prompt box; on invoke, wrapped in a short explicit-instructions preamble and dispatched through synthSend's existing forced-prompt parameter as one message. The dispatch engine already exists — fixPreviewErrors, fixPythonErrors, deploy fix-in-chat, and the Lighthouse queue are four shipped internal workflows with no user authoring surface. Plus zero-token "save as workflow" from the current turn's prompt + s.activity tally (authoring from data in hand, no model call). (L13)

CL-14 · Generic OpenAI-compat provider — compat: model prefix resolved to a user-configured base URL + optional safeStorage key, reusing the mlx path's OpenAI-compat client. Settings CRUD: name, URL, optional user-entered $/Mtok. The meter always shows tokens; $ only when the user priced it — explicit "$ unknown — set a price" otherwise, never fabricated (L2, L17). Opens OpenRouter/Groq/DeepSeek/LM-Studio/self-hosted without shipping 40 integrations. (Subscription-OAuth piggybacking — Cline's other adoption lever — is not reachable this way; it stays on the watchlist, see §5.)

CL-15 · Renderer↔main version handshake — The critic generalized our "pervasive" restart-required gotcha (push, staging, checkpoints, chatStore columns — and CL-06/16/18/21 each add new IPC): a build stamp exchanged at boot; mismatch shows one "Yee needs a full restart" banner everywhere, instead of per-feature tripwires and mystery staleness. Small, and it should land before the IPC-adding features.

P2 — open doors, wider capture

CL-16 · MCP client v1 — STDIO-only, local servers from .yee/mcp.json + global config; main-process lifecycle patterned on devServer.js; tools mirrored into the text-protocol vocabulary, frozen at chat creation (same fixed-at-creation cache invariant as language groups). Every call dispatches through executeTool behind a new gate class: default always-ask modal labeled server:tool, per-tool session allow, results through compactResult, every call in s.activity (PR bodies honestly show third-party tool use) and metered (L14, L2). Manual JSON config + 1-2 documented example servers; explicitly no marketplace. Default-off until the approval UX is click-through verified. Sequencing (critic): the nativeTools.js mirror path is marked untested on the system map — v1 rides the text-vocabulary path only; native mirroring waits for native mode's own verification. Retires the README's honest "no plugin system" gap.

CL-17 · .yee/ignore enforced at the boundary — Gitignore-syntax exclusion (secret-pattern defaults on) enforced inside executeToolDirect for read/grep/list/write, in repoMapFor, and in captureEdit so excluded paths never enter the SFT warehouse. Blocked access returns a structured "path excluded" tool result so the model reroutes. The safety net still sweeps everything — ignore governs model access and training capture, never recovery (guard-test this; the 2026-07-18 scare makes sweep completeness sacred). Skips Cline's advisory-phase mistake entirely (L15). Header disclosure when active.

CL-18 · Resume interrupted turn — Tiny in-flight marker per session; on load after a mid-turn death, an honest note + one-click resume building a continuation prompt from the s.activity tail + dirty-diff against the turn's baseline checkpoint, through the normal synthSend path.

CL-19 · Per-turn receipt line — Collapsed one-liner in the message footer: tokens in/out, cache-hit%, ~$, N tool calls (M edits), files touched — all from data the turn already counts, formatted to match the PR body's stat line so the live chat and the PR reviewer read the same numbers (the instrument agreeing with itself across surfaces).

CL-20 · fetch_web tool — Text-based HTML→markdown fetch through the executeTool choke point: gated (approval class "network read"), metered, compacted. The light alternative to a browser tool; Cline ships this (v3.48) and we have no web retrieval at all. No search API dependency in v1 — URL fetch only (search needs a provider and is a separate decision).

CL-21 · Multimodal attach — Drag-drop/paste an image into the chat (mockups, bug screenshots — a core easy-mode use case the critic flagged). Rides the existing screenshot→chat vision path incl. the honest token-estimate chip (⌈w/28⌉×⌈h/28⌉); non-image files deferred.

CL-22 · Portable .yeepack bundles — Export a stream/pack (wd/rm/pa + params + lineage) as versioned JSON, committable to .yee/packs/; import shows as a fork labeled "imported — unmeasured", excluded from default until the user's own measured promotion clears it (L5). Author scores display as advisory only; local promotion always re-measures. Marketplace-lite with the discipline Cline's marketplace lacks; pairs with the data library's export-bundle direction.

P3 — someday / watchlist

  • CL-23 · Zero-cost repo memory brief — "Previously in this repo" composed from persisted sessions/activity/authored/DAG, optionally distilled by local mlx; rides the turn-1 rider; user-visible + toggleable. Memory Bank's value without its per-task frontier tax (L18 — and per the critic: the honest claim is "zero-config built-in over data we already persist," not "structurally impossible for Cline").
  • CL-24 · Read-only research subagents — fan out exploration to read-only agents with own budgets, results summarized into the main context. Real value for repo exploration burning the 24-turn window, but heavy; after ledger + handoff prove out.
  • CL-25 · Skills-style progressive disclosure — tier-3 instruction packages loading on demand via the user prompt (cache-safe). After CL-07/13 establish the config-loader + authoring habits.
  • CL-26 · General pre/post-tool hooks — the single choke point makes this cheap someday; CL-17 is the concrete instance first. fail_closed semantics from day one.
  • Watchlist, no commitment: subscription-OAuth piggybacking (adoption lever, but ToS/complexity-fraught; revisit if BYO-key friction shows up in real usage) · Python Space/Jupyter completion (Cline ships cell-granularity notebooks; ours is PARTIAL — explicitly deferred, not forgotten, pending Ty's call) · local-whisper dictation · edit-and-regenerate of past messages.

Explicitly NOT pursuing (decided, with reasons)

  • Tab completion — Ty's standing call; Cline's reviews confirm agent-does-the-work can win without it.
  • CLI/TUI, Kanban, Agent Teams, connectors (Slack/Discord/Linear), cron, enterprise SSO — Cline's breadth wave serves their platform play; our thesis is depth on the local-first editor + Forge ecosystem.
  • YOLO mode as a labeled feature — easy mode already auto-applies within a checkpointed sandbox; what we need is the disclosure (CL-05), not more throttle.
  • MCP marketplace — manual config first (Cline's own maturity order); a marketplace without the measured-substrate discipline would dilute L5.
  • Anything requiring a paid eval run to build or justify — standing rule; every metric above is organic-usage-derived.

4. Suggested milestones (sequencing, not new scope)

  • M1 — Trust the loop (all S/M, ~quick): CL-03 (bug), CL-02, CL-01, CL-07, CL-10, CL-15. Outcome: no silent dead-ends, recovery inline, rules honored, walk-away-able runs.
  • M2 — Long-run control: CL-05, CL-04, CL-06, CL-08, CL-09, CL-19. Outcome: onboarding ramp + honest machinery + context continuity.
  • M3 — Verify everywhere, measure honestly: CL-11, CL-12 (+ s.activity honesty fix), deploy-check manifest reuse (Cargo/poetry via pushSelect's LOCKFILE_PAIRS — cheap, critic-flagged), Lighthouse route context. Outcome: the verification moat covers non-web repos; apply-success becomes our public-grade health metric.
  • M4 — Open doors: CL-13, CL-14, CL-21, CL-20, CL-17. Outcome: repo-resident ecosystem + any-endpoint BYOK, exclusion enforced before capture widens.
  • M5 — Ecosystem bets: CL-16, CL-22, CL-23, then P3 re-rank. Outcome: MCP behind the spine; sharing with the measured-substrate discipline.

Candidates for the 🗺 PRIORITY TRACKER (Ty's call): CL-01, CL-02+03, CL-07 as the P0 block; CL-11 as the thesis-completing P1.


5. Standing engineering contracts every item above obeys

  1. Cache invariant: nothing ever mutates the byte-stable system prompt; injection = turn-1 pinned rider or follow-up prompts; per-chat vocabulary freezes at creation. The <30% cache-read tripwire is the regression alarm.
  2. Choke-point contract: every tool — built-in or MCP — dispatches through executeTool, so gates, s.activity, farm, and the meter see everything.
  3. Safety-net completeness: the shadow-git sweep captures everything, always; no ignore/exclusion feature may narrow recovery. Guard-tested.
  4. Honest degradation: when a subsystem is down or a limit is hit, say so in the chat surface; never silently degrade (compaction, iteration caps, unknown pricing, truncated activity).
  5. Verify at the product surface: each shipped item's acceptance criterion is a live-app click-through, not a green unit test (SHIPPED_PACKS rule). Items building on system-map untested nodes (native tools, Lighthouse fix queue) sequence that verification first.
  6. No paid runs: all metrics are organic-usage-derived on users' own keys; imported/shared config is unmeasured until the user's own promotion clears it.

Appendix: primary sources

  • https://docs.cline.bot — core-workflows (plan-and-act, checkpoints, using-commands, working-with-files), features (auto-approve, auto-compact, focus chain, multiroot), enterprise docs
  • https://github.com/cline/cline — README + CHANGELOG (v3.1 → v4.0, Jan 2025 → Jun 2026)
  • https://cline.bot/blog — v3.25 (Focus Chain, Auto Compact, /deep-planning), v3.31 (YOLO, task header), v3.48 (Skills, websearch), v4.0 (SDK re-platform)
  • Reviews/community 2025-2026: cost-blowup complaint record, context-regression issue #5616, Roo Code fork arc (archived May 2026), Plan/Act reception threads
Open →
general YeeYee Devlog, July 2026 — Token Honesty, Git Manners, and a Notarized Mac App Jul 29, 2026

A changelog for everyone following along. Last time, the whole project arc told the full story of YeeYee — a local-first, offline-capable AI code editor whose thesis is the agent does the work — and ended at roughly "feature-complete, validation remaining." This is what happened in the seventeen days after that post. Short version: feature-complete became shippable, and dogfooding taught us more than any benchmark did.


TL;DR

  • YeeYee is now a real Mac app. Signed, notarized by Apple, Gatekeeper-clean DMG. Double-click,

drag to Applications, done — no right-click-to-open ritual.

  • The token meter now tells the truth. Cost estimates used to run ~7× high; they now match the

provider's own console to within pennies. A prompt-cache bug that was silently re-writing context on every call got found, proven by replay, and killed — one measured session dropped from $0.60 to about $0.10.

  • The agent learned git manners. It commits only what it actually touched, fact-checks "will this

deploy?" before every push, does real rebases with a recoverable safety net, and writes pull-request descriptions that tell the story of the conversation that produced them.

  • The live preview got street smarts. Multi-app repos get a launcher, backend services are treated

as services (not broken web pages), port conflicts get diagnosed instead of "fixed," and Lighthouse scores sit in the chat header with a one-click path to fixing them.

  • We stopped running paid internal evals — and shipped the measurement tooling to users instead.

More on why below; it's the most honest decision in this post.


1. Shipping a real Mac app (July 12 → 25)

The arc post ended with an editor that worked great — on the machine it was built on. Getting from there to "a stranger double-clicks a DMG and it just works" turned out to be its own project.

  • July 12 — a one-command installer build, a launch-readiness audit that surfaced four

ship-blockers (all fixed), and a safety-hardening pass over the surfaces that could actually hurt a user: file edits, secrets handling, the terminal, and the embedded webview.

  • July 21 — a turnkey code-signing and notarization pipeline: one command preflights the

credentials, signs with hardened runtime, notarizes, staples, and verifies the result.

  • July 25 — the first fully notarized DMG, verified accepted — Notarized Developer ID at both

layers. Plus the cosmetics a real app deserves: a proper app icon and a styled installer window instead of a bare dark rectangle.

Two hard-won lessons for anyone shipping an Electron app on macOS, because these cost us real days and the internet's answers were thin:

  1. Apple's G2 intermediate certificate must live in a persistent keychain. Modern Developer ID

certs chain through an intermediate that macOS doesn't ship. With it missing, codesign fails intermittently — roughly four runs in five — which means one lucky green build proves nothing. One certificate import and the pipeline went thirteen-for-thirteen.

  1. Notarizing the app is not notarizing the DMG. The standard tooling happily notarizes the .app

inside while leaving the DMG container itself unsigned — and Gatekeeper judges the thing the user actually downloads. Sign, notarize, and staple both layers, and verify both.

And then dogfooding the installed app immediately paid for itself: launched from the Dock, the live preview died with spawn npm ENOENT. Apps launched from Finder get the OS's bare PATH — not your shell's — so every developer tool the app shells out to is invisible. The fix (capturing the user's real login-shell environment at boot, the way the big editors do) closed the same hole in three other features that would have quietly broken for every beta tester.

2. The token meter now tells the truth (July 16 → 18)

If you've read the earlier posts, you know the standing rule here: no headline without a measurement. This month the measuring instruments themselves got audited, and the findings were humbling in both directions.

  • Cost estimates ran ~7× high. The meter priced every token at one blended rate; real pricing

differs per model, per direction, and per cache status. Rebuilt with real per-model, per-token-type pricing and verified against an actual provider bill: a session the old meter called ~$25 prices to ~$3.50 — matching the console. Every dollar figure in the app (meter pill, per-conversation spend, usage history) corrected at once.

  • The prompt cache was being wasted. Console data from a real session showed only 4% of input

tokens read from cache — nearly the whole bill was cache writes, paying the premium and never collecting the discount. A replay investigation proved the mechanism (a history-trimming step was shifting the message prefix on every call, invalidating the cache each time), and the fix took that session's measured cache reads to the ~85–95% range. Honest saving on that one receipt: about 6× — $0.60 down to roughly $0.10. That's a receipt, not a benchmark; your sessions will vary.

  • Some telemetry was dead and nobody knew. Months of usage, zero rows of token history on disk —

the plumbing between the model provider and the storage layer was silently dropping usage data for an entire routing path, including local models. Now fixed, normalized at one boundary, and locked with tests so it can't silently regress. Lesson worth stealing: when a cost surface reads empty, suspect the plumbing before the storage.

Around the meter, the spend-control story rounded out: every conversation permanently carries its own token and dollar totals, the start screen shows a year-view usage tree, a spend circuit-breaker pauses runaway loops — and you can always stop. The send button becomes a stop button mid-run, and the budget pause now has a real "stop here" that keeps the work already done.

The chat header pill now reads tokens · cache-hit % · estimated $ at a glance, with a tripwire that warns if cache churn ever creeps back.

3. Git manners: the agent stops treating your repo as its property (July 21 → 25)

This is the section I'd most want other AI-editor builders to read. The blunt product feedback that kicked it off: the agent treated the entire working tree as its own. Every AI coding tool we've tried does some version of this. YeeYee now doesn't:

  • It publishes only what it authored. The agent tracks which files it actually edited, and a push

stages exactly the selection you approve. Files changed outside the chat — by you, by another tool — survive branch swaps intact and appear at push time clearly labeled "changed outside this chat," unchecked by default. The safety-net checkpoints still capture everything (recovery keeps working); publication is deliberate.

  • "Will this deploy?" is checked before you push. Born from a real scar: a mechanically-successful

push once shipped a dependency-manifest change without its lockfile, and a production site of ours served a stale build for two days while every signal looked green. Now an always-on, sub-second deployability check runs after every edit, a full local deploy build is one click away, and the push dialog blocks a known-broken push — with a mechanical, zero-AI-tokens one-click lockfile fix, because a deterministic problem should never cost you an agent turn.

  • Real rebase, with a safety net. The visual branch canvas already had fork, swap, and merge;

now it has rebase — powered by git's actual rebase machinery underneath, not a hand-rolled replay. The originals stay pinned and recoverable, conflicts reuse the same three-way resolution UI as merge, and a half-finished rebase survives an app restart.

  • A real-git health banner. People bounce between editors and terminals all day; none of them talk

to each other. YeeYee now warns — visibly, above the chat — when you're behind your branch's upstream, on a detached HEAD, or sitting on a rebase another tool left mid-flight. It never blocks and never auto-runs anything: the suggested fix is typed into the terminal for you to review and run.

  • Pull requests that tell the story. A YeeYee-opened PR now carries what was asked, what the agent

did (a tool-by-tool tally), which files changed, and the full transcript collapsed at the bottom — synthesized from data already in hand, zero extra tokens.

The receipts, because process is the product here: this batch went through adversarial multi-agent review before ship, and those reviews caught 48 real bugs — including a rebase edge case that could have clobbered gitignored files and a stash sequence that could have stranded uncommitted work. Every one was fixed and got a regression test before a user ever touched the feature. Agent-found bugs are cheaper than user-found bugs; that's been true every single week of this project.

4. The preview grows up (July 15 → 19)

The live preview is the heart of the "agent does the work" loop, and it collected a month of street smarts:

  • Multi-app repos get a launcher. A repo with several runnable apps now shows you the choices;

swapping the preview target is one click.

  • A service is not a broken web page. Previewing a backend service or crawler as if it were a

website once produced a million-token agent no-op — the agent dutifully trying to "fix" a page that was never meant to render. Services are now recognized and presented as services. That single distinction deletes an entire class of wasted spend.

  • Port conflicts are diagnosed, not "fixed." Dev servers that auto-hop to a free port are allowed

to; a genuinely stuck port is reported as an environment problem — naming which of your own running apps is holding it — and is never fed to the AI as a code bug to fix. (Previous behavior, confession: it once killed a healthy server one log-line before that server printed its URL.)

  • Lighthouse in the chat header. Four real scores — performance, accessibility, best practices,

SEO — live next to your conversation, and any score below 100 is one click from a chat turn that goes and fixes it.

  • Smaller but daily-life: the Python notebook's run button is now boringly reliable (plus an explicit

"clear output"), the element picker supports multi-select with color-paired references you can discuss in chat, and the model dropdown grew provider groups, sensible recommendations, and recents — including same-week support for the newest frontier models.

5. The honest-instrument decision: we stopped paying for our own evals (July 16)

The arc post ended with two features "code-complete, awaiting paid evaluation runs." Here's the update, and it's not the one we expected to write: those runs are on hold indefinitely — and that's the honest call.

The measured record earned it. Our per-language tuning lever demonstrably rescues weaker models — that result was real and statistically significant — but on frontier models the same lever was either noise or robustly cost more. More eval cells weren't going to change what we ship; they were going to burn money confirming a curve we could already see.

So the instrument shipped to the people it actually serves: split-testing is now the users' tuning surface. You run splits on your own keys against your own repo, and the same honest statistical gates we built for ourselves — minimum sample floors, bootstrap confidence intervals, replay guards — protect your measurements. A winning configuration gets promoted into your model dropdown for your repo's language group. Firsthand use of a launched product teaches more than another synthetic benchmark cell. The standing rule is unchanged: no headline without a measurement.

Two transparency features shipped in the same spirit:

  • The system map — an interactive network diagram of the entire application (38 nodes, 56

connections), now embedded in the app itself, where every node honestly shows its status: shipped, partial, or untested. Most software hides that; we think showing it is the product.

  • The Forge door — the live model-training dashboard (the mine → train → evaluate loop from the

arc post) is now a door on the start screen, not a separate tool you have to know about.


What's next

Beta DMGs are going into testers' hands. The newest git surfaces are bench-proven and headed for live click-through. Hard mode is visible on the launch screen behind a "coming soon" gate. And we'll keep publishing the methodology — the metering math, the statistics, the process failures — because the receipts are the point.

Previously: the whole project arc. YeeYee is software developed by dev3lop.com.

Open →

Building j7 — a minimal place to chat, blog, and keep a portfolio.

j7, by dev3lopcom, llc