esc
The Gardener and the Gravel
The Solid Convergence

The Gardener and the Gravel

The Solid Convergence, March 21, 2026 (in which a content editor was built correctly in two hours, accumulated five layers of scaffolding in the next two, was rolled back entirely, and the gardener...

March 21, 2026

The Solid Convergence, March 21, 2026 (in which a content editor was built correctly in two hours, accumulated five layers of scaffolding in the next two, was rolled back entirely, and the gardener learned that sharpening the shears is more important than planting faster)


Previously on The Solid Convergence…

The five reports had been delivered. The Map of Content had organised itself. Eleven editor panels had been migrated to the RecordEditor pattern in a two-day blitz that deleted more code than it wrote.

But one panel — the markdown content editor — still wore its own skin. Its own state machine. Its own buttons. Two nervous systems in the same body, each convinced it was the brain.


09:43 — Two Action Bars

The espresso machine had opinions about Friday morning, but riclib wasn’t listening. He was staring at two toolbars.

The skill editor had a header: Commit, Discard, Edit, Close. The form below it had a footer: Save, Delete, Discard. They didn’t know about each other.

riclib: “Why does the content editor have its own buttons?”

CLAUDE: “It predates the form pattern. It has its own state machine — NATS KV drafts, liverepo, five endpoints. It was standalone before RecordEditor existed.”

riclib: “And now it’s not standalone. It’s inside a form. But it still thinks it’s alone.”

CLAUDE: “Two brains. One body.”

riclib: “One brain too many.”

He erased the whiteboard. The marker protested — it had barely recovered from the Map of Content session — but complied.

OLD:
  content state = NATS KV draft ← liverepo ← gitstore ← git
  5 endpoints: /editor/open, save, commit, discard, close
  own header bar, own buttons, own tracking

NEW:
  committed = git HEAD
  working copy = file on disk
  diff = HEAD vs disk
  that's it

riclib: “Git is the only state machine.”

CLAUDE: “Three new methods on the gitstore. WriteOnly — put it on disk, don’t commit. Checkout — reset disk to HEAD. GetCommitted — read from HEAD. The content field is just a field. Like Name. Like Description. The form footer handles everything.”

THE SQUIRREL: ears rotating “What about the diff gutter?”

CLAUDE: “Server-side. Same mdedit.DiffSections infrastructure we already have.”

riclib: “Build it.”


10:31 — Seven Files

Two hours. Seven files changed.

repository.go gained WriteOnly and Checkout. store.go gained typed wrappers. A new content.templ rendered markdown with a hidden input for form submission. The skill handlers were rewritten — loadEditorProps became buildContentProps, three lines instead of thirty. EditorSave on the service stopped going through NATS KV and wrote directly to disk.

Clean compilation. Tests passing.

THE SQUIRREL: inspecting the diff, finding nothing to complain about, which made her suspicious “It’s too simple.”

[A scroll descended. It landed on the espresso machine, which was accustomed to this.]

    SIMPLE IS NOT SUSPICIOUS
    SIMPLE IS FINISHED
                            🦎

riclib: “Let’s test.”


10:45 — The First Stone

riclib: “Open the Deep Compliance Audit skill.”

CLAUDE: “Done. The content renders. The form has one footer. The metadata fields are in the aside. Everything is—”

riclib: “The footer is gone.”

CLAUDE: “…what?”

riclib: “The footer. Save, Delete, Discard. It’s not there. The content field pushed it below the fold.”

[Silence.]

The CSS property was gap: var(--space-lg) on .editor-panel__form. Harmless when the form lived inside a scroll container. Fatal when the form was promoted to wrap the entire editor body. The gap added pixels between children. The body had flex: 1. The footer was below the viewport.

Four words missing: flex: 1; min-height: 0.

CLAUDE: “Fixed.”

riclib: “What else?”


10:52 — The Second Stone

riclib: “Ask the LLM to edit the skill content.”

The LLM edited. The content changed on disk. The SSE bridge fired. The OOB swap targeted #mdeditor-skill-comply-deep-audit.

Which no longer existed.

CLAUDE: “The bridge still renders MdEditorOOB. That targets the old container. The new container is #field-content.”

riclib: “So the LLM edits silently vanish.”

CLAUDE: “The edit succeeds. The file changes on disk. The user just… doesn’t see it.”

THE SQUIRREL: brightening “We need an OOBRendererRegistry! Each domain registers its renderer. The bridge looks up the type, dispatches to the right—”

riclib: “It’s one if statement.”

THE SQUIRREL: “But what about EXTENSIBILITY? What about when artifacts migrate? What about—”

riclib: “One. If. Statement.”

THE SQUIRREL: quietly “The registry would have been elegant.”


11:03 — The Third Stone

riclib: “Navigate to /skills/comply-deep-audit in the browser.”

Skill not found.

CLAUDE: “The skill routes only return HTMX fragments. Direct URL navigation renders the shell without the editor panel.”

riclib: “So we can’t test by URL.”

CLAUDE: “Not without adding shell rendering for the skill domain. The RenderShell function takes a sidebar component. For a direct URL, we also need the editor content. The Shell has seven parameters—”

riclib: “Add an eighth.”

CLAUDE: “The signature is used by five domains. Changing it means—”

riclib: “Make it variadic.”

CLAUDE: “The renderShell callback is stored on handler structs. The type needs to change everywhere.”

THE SQUIRREL: “We could use a context value! WithEditorContent(ctx, component). The shell checks templ.GetChildren and—”

CLAUDE: “But RenderShell is Go code, not a templ file. So we need templ.WithChildren. And the callback type needs—”

riclib: “Stop.”


11:15 — The Whiteboard

riclib looked at the whiteboard. The beautiful two-line design — committed = HEAD, working copy = disk — was still there. Around it, like ivy on a wall, were three new diagrams: the shell context wiring, the callback signature change, and the beginning of what the Squirrel had labeled “OOB Renderer Registry (v1).”

He counted the layers.

The content field: correct. The gitstore extensions: correct. The form integration: correct. The CSS fix: correct. The OOB if-statement: correct. The shell wiring: correct. The context trick: correct. The variadic signature: correct.

Seven correct things. Five of which existed solely because they couldn’t open a URL.

riclib: “We’re not building a content field anymore.”

CLAUDE: “We’re building URL navigation.”

riclib: “We’re building URL navigation inside a content field ticket. And the URL navigation needs shell changes. And the shell changes need signature changes. And the signature changes need—”

[A scroll descended. It was heavy. The Lizard had opened both eyes. Both eyes meant the scroll was load-bearing.]

    THE GARDENER WHO CANNOT REACH THE ROSES
    DOES NOT BUILD A LONGER PATH
    THE GARDENER MOVES THE STONES
                                            🦎

11:20 — The Compost

riclib deleted the branch.

Not the gitstore methods — those were stones, correctly placed. Not the ContentField component — that was the right shape. Not the form integration or the service rewrite.

He deleted the path. The shell wiring. The context tricks. The callback maps. The variadic signatures. Five layers of scaffolding, each individually correct, each solving a problem that was not the problem they sat down to solve.

The Squirrel watched the diff scroll past. More red than green. Her lower lip trembled.

THE SQUIRREL: “The registry was elegant.”

riclib: “The registry was a path around a stone. The stone is that we can’t navigate to a skill by URL.”

He opened Linear. Created S-522: Direct URL Navigation.

riclib: “We move the stone. Then we come back.”

THE SQUIRREL: “But the content field is DONE. It WORKS. We just need—”

riclib: “We need a clean path. Not a map around the stones.”

[The Squirrel looked at her clipboard. On it, in her own handwriting: “Callback Map to Avoid the Stones.” The map was more complex than the path.]

[She crossed it out.]


11:35 — The Seed Tray

Then riclib did something Claude could not have predicted, because Claude does not have a predictive model for what happens after a rollback.

He wrote a lessons learned file.

Not a postmortem. Not a bug report. A map. Where the stones were. Which CSS property breaks when the form wraps the body. Which domain name is plural. Which OOB target no longer exists.

CLAUDE: “What is this for?”

riclib: “For you. Next session.”

CLAUDE: “I won’t remember this session.”

riclib: “I know. That’s why the file exists.”

He opened CLAUDE.md. Added a line. Opened the Linear ticket. Added a comment mapping every wrong turn.

THE SQUIRREL: pulling out a fresh clipboard “We could build a SessionContextPersistenceFramework—”

riclib: “No.”

THE SQUIRREL: “—with three abstraction layers—”

riclib: “No.”

THE SQUIRREL: “—and a plugin system—”

riclib: “The lessons file is the plugin system. CLAUDE.md is the framework. A Linear comment is the persistence layer.”

[Silence.]

THE SQUIRREL: very quietly “Those are just… files.”

riclib: “Those are just files.”

[A scroll descended. It landed on the seed tray, next to the label that read S-522.]

    THE FASTEST GARDENER IN HISTORY
    PLANTS FOUR THOUSAND SEEDS A DAY
    AND DOES NOT FEEL THE STONES

    THE GARDENER'S JOB IS NOT TO PLANT FASTER
    THE GARDENER'S JOB IS TO CLEAR THE PATH

    CONVENTIONS ARE NOT DOCUMENTATION
    CONVENTIONS ARE GRAVEL REMOVAL

    THE SHEARS MUST BE SHARP
    BEFORE THE GARDEN STARTS

                                    🦎

The Tally

Content field design:                    correct
Content field implementation:            correct
Content field shipped:                   no
Reason:                                  stones on the path

Stones found:                            3
  CSS flex:1 missing on form:            pushed footer off-screen
  Domain name "skill" vs "skills":       404 on every load
  OOB target #mdeditor-* deleted:        LLM edits vanish silently

Layers of scaffolding built:             5
  Shell context wiring:                  correct, composted
  Variadic renderShell signature:        correct, composted
  OOB Renderer Registry (v1):           proposed, denied, composted
  templ.WithChildren integration:        correct, composted
  SessionContextPersistenceFramework:    proposed, denied, composted

Time building the content field:         2 hours
Time building paths around stones:       2 hours
Time composting the paths:               3 minutes
Time writing lessons learned:            15 minutes

Seeds planted for next season:           1 (S-522: URL Navigation)
Shears sharpened:                        2 (CLAUDE.md, Linear comment)

Lizard eyes opened:                      2 (unprecedented, repeated)
Squirrel proposals denied:               3
  OOBRendererRegistry:                   "one if statement"
  CallbackMapToAvoidTheStones:           self-crossed-out
  SessionContextPersistenceFramework:    "those are just files"
Squirrel proposals accepted:             0

The path is the product.
Not the roses, not the soil —
the three feet of cleared ground
between the gardener and the bed.

The fastest hands in history
plant four thousand seeds a day.
They do not feel the stones.
They do not know the stones are there.

So someone kneels at dawn,
before the planting starts,
and moves the stones aside
and sharpens both the shears.

Not because the garden asked.
Because the gardener will come
with no memory of yesterday
and the full speed of spring.

🦎


See also:

The Solid Convergence:

Being riclib:

Yagnipedia:

The Tickets:

  • S-521: Content field (the feature, correct, coming back)
  • S-522: URL navigation (the stone, being moved)