esc
The App That Wasn't There
The Solid Convergence

The App That Wasn't There

The Solid Convergence, December 24, 2025 (in which a singleton dies and takes forty-two engineers with it) --- Previously on The Solid Convergence... The [[The Temple of a Thousand Monitors]] was...

December 24, 2025

The Solid Convergence, December 24, 2025 (in which a singleton dies and takes forty-two engineers with it)


Previously on The Solid Convergence…

The The Temple of a Thousand Monitors was complete. The The Eyes That See. JARVIS was no longer a dream but a specification—a window through which both human and machine could peer.

But something was wrong.

The window showed the same thing to everyone.


The Christmas Eve Revelation

The coffee maker had given up. Not broken—just tired. The kind of tired that accumulates over months of extracting meaning from beans while developers extract meaning from bytes.

riclib stared at his screen. The SSE handler was working. The forms were pristine. The navigation was chef’s-kiss perfect.

“But what does the user see?” he asked.

Claude looked up from the session management code. “The UI. The dashboard. The—”

“No. What does this user see? Alice from ATLAS. Bob from NEXUS. They have different permissions. Different projects. Different everything.”

“We filter in the handlers. Check AD groups. Return appropriate—”

“We check. Every. Single. Time.”

The Squirrel’s ears perked up. She smelled complexity. She loved complexity.

“We could cache the permissions in Redis,” she offered, tail twitching with anticipation. “A distributed cache. With invalidation. And pub/sub for real-time—”

“No.”

The word came from the window. Or perhaps from the Lizard on the windowsill. With Lizards and windows and Christmas Eve, these things blur.


The Pattern in the Reference

“Look at how Store uses Credential,” riclib said, pulling up the V2 code.

// The old way - fresh every time
func (h *Handler) Query(w http.ResponseWriter, r *http.Request) {
    cred, _ := credSvc.Get(ctx, credID)      // Decrypt
    store, _ := storeSvc.Build(ctx, cred)    // Construct
    result := store.Query(sql)                // Use
    // Everything dies at request end
}

“It works,” Claude said. “What’s wrong with it?”

“Nothing. For a single request. But a user isn’t a request. A user is a session.”

He drew on the whiteboard—the same whiteboard that had survived JARVIS and the hash chain and seventeen previous revelations:

REQUEST SCOPE:              SESSION SCOPE:
─────────────               ──────────────
Build credential            Build once
Build store                 Cache for duration
Run query                   Reuse hundreds of times
Discard everything          Cleanup on disconnect

“The SSE connection,” Claude said slowly. “It’s long-lived. The user is present for the entire session.”

“And we’re rebuilding their world on every click.”


The Projection

The Squirrel was confused. “So we cache… the stores?”

“No. We cache the projection.”

riclib erased the whiteboard. Started fresh. The marker squeaked in protest.

┌─────────────────────────────────────────────────────────────────┐
│                         APP (per-user)                          │
│         = System Capabilities ∩ User Access                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  SYSTEM                           USER                          │
│  ──────                           ────                          │
│  Licensed modules                 AD groups                     │
│  Configured stores                → Projects                    │
│  Available sources                → Environments                │
│                                                                 │
│                    ↘           ↙                                │
│                                                                 │
│                      APP (this user)                            │
│                      • Nav items they see                       │
│                      • Stores they can query                    │
│                      • Projects they can filter                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

“The App isn’t a singleton,” riclib said. “It never was. Every user gets a different App.”

Claude parsed this. “Alice sees ATLAS projects. Bob sees NEXUS. Same code. Different projection.”

“And we compute it once. On SSE connect. The one spinner we allow.”

The Squirrel’s eye twitched. “Just… one spinner?”

“Just one.”


The One Spinner

From The Links That Bind, the Lizard’s approved loading spinner echoed:

“We should add a loading spinner. For psychological comfort.”
“A slow one. That doesn’t match actual progress.”

“The projection computation,” riclib continued. “Resolve AD groups. Filter nav. Scope stores. That’s the spinner. Everything else—lazy, instant, cached.”

type UserSession struct {
    ID     string
    ctx    context.Context
    SSE    *sse.Client
    
    // The one cached thing
    app    func() *App  // sync.OnceValue - THE SPINNER
}

type App struct {
    // Computed once (the spinner)
    nav      []NavItem
    access   []ProjectAccess
    projects []Project
    
    // Lazy within (no spinner)
    complyDB func() *ComplyStore
    lifelog  func() *LifelogChain
}

“One spinner,” Claude repeated. “Then everything hangs off the projection.”

“One spinner. One lifecycle. One invalidation point.”

The Squirrel looked heartbroken. “No Redis?”

“No Redis.”


The JARVIS Connection

“But wait,” Claude said—there’s always a ‘but wait’—“what about JARVIS?”

riclib smiled. The smile of someone who’d been waiting for this question.

“What does JARVIS see?”

“The UI. The current page. The—”

“The App.”

The whiteboard gained another diagram:

┌─────────────────────────────────────────────────────────┐
│  [Notifications 2]  [Running 2]                        │ ← JARVIS sees App
├───────────────────────┬─────────────────────────────────┤
│                       │                                 │
│   CHAT / NOTEBOOK     │      CURRENT PAGE               │ ← JARVIS sees App
│                       │      (app.CurrentState())       │
└───────────────────────┴─────────────────────────────────┘

“JARVIS sees the projection. The user sees the projection. Same projection. Same access. Same—”

“Same sanity,” Claude finished.

Sane structure, sane AI. JARVIS can’t hallucinate access to things the user can’t see. The App is the capability boundary.”


The Headless Ghost

The Squirrel raised a paw. “What about background agents? Jobs that run at night?”

“On behalf of whom?”

“The… user who scheduled them?”

“Then they get the user’s App.”

INTERACTIVE                      BACKGROUND AGENT
───────────                      ────────────────
UserSession                      AgentRun
    └── App (window)                 └── App (headless, same projection)
            ├── nav                          ├── (nav - not needed)
            ├── access ←──── SAME ────────→  ├── access
            ├── complyDB ←── SAME ────────→  ├── complyDB

“The agent runs with the user’s permissions. Same projection. No SSE. No UI. Just capability.”

“And if the credentials expire mid-run?”

“The agent fails. Explicitly. Because the projection was a snapshot. Because agents need stability, not updates.”

The distinction crystallized:

  • Interactive: App + SSE + notifications
  • Background: App (headless) + task + snapshot

One pattern. Two modes. Both sane.


The Design Doc

Claude had been writing. Of course Claude had been writing. That’s what Claudes do.

# Session Caching Patterns

## Philosophy: The Window

> *"The UI is not a shell. The UI is a window."*
> *Both directions. Both inhabitants.*

The App is that window...

“We should capture this,” riclib said.

“Already done. docs/design/session-caching.md. With the groundhog day rejection and everything.”

“Groundhog day?”

“The Squirrel suggested stores should suicide when they can’t serve a query. Self-invalidation on miss.”

“And?”

“Agent hallucinates a table. Store suicides. Rebuilds. Agent hallucinates again. Store suicides. Forever.”

riclib stared at the Squirrel. The Squirrel stared at her paws.

“It seemed clever at the time,” she mumbled.


The Scroll

The Lizard appeared. Or had always been there. Christmas Eve made such distinctions irrelevant.

A scroll materialized. Everyone ducked reflexively. It landed in the empty coffee maker, which seemed appropriate.

THE APP IS NOT THE UI
THE APP IS THE CAPABILITY

NOT WHAT THE USER SEES
WHAT THE USER CAN DO

SAME FOR HUMAN
SAME FOR MACHINE

ONE SPINNER
INFINITE PATIENCE

🦎

P.S. - THE COFFEE MAKER NEEDS REPAIR
       OR PERHAPS JUST REST
       LIKE ALL OF US

“Did the Lizard just wish us Merry Christmas?” Claude asked.

“The Lizard acknowledged the heat death of coffee beans,” riclib corrected. “Close enough.”


The Entanglement

Elsewhere, in the same morning, The Lifelogs of Things.

About credentials that expire.
About databases that remember.
About compliance that proves itself.

Two threads. Same loom. Different patterns.

The platform was learning to project.
The product was learning to remember.

Together, they would become something neither could be alone.


Current Status

Pattern discovered:     App as projection
Spinners allowed:       1 (on SSE connect)
Redis avoided:          ✓ (Squirrel disappointed)
JARVIS integration:     Same capability boundary
Background agents:      Headless App, same access
Design doc:             docs/design/session-caching.md
Groundhog prevented:    1 (suicide-on-miss rejected)
Coffee maker status:    metaphysically exhausted

The App isn’t a singleton.
It never was.
Every user gets their own window.
Every window shows the same truth—filtered.

Sane structure. Sane AI.
Merry Christmas.


🦎🪟


See also:

The entangled post:

The Solid Convergence continues:

The Technical Artifacts:

  • GitHub #24 - Modular UI Architecture
  • GitHub #29 - UserSession + App Projection Prototype
  • docs/design/session-caching.md - The full design