---
title: "Architecture and pitfalls met"
url: https://phatdev.net/en/docs/aiusage/kien-truc
lang: en
updated: 2026-10-23
alternate:
  vi: https://phatdev.net/docs/aiusage/kien-truc
markdown: https://phatdev.net/en/docs/aiusage/kien-truc.md
---

# Architecture and pitfalls met

Part of the doc set: [AIUsage](https://phatdev.net/en/docs/aiusage.md)

> The data flow, the components, cache rules and the engineering pitfalls met.

This page is for readers who want to understand or change the code: the data flow, the components and the pitfalls met along the way.

## Data flow

```
claude.ai · Anthropic Console · chatgpt.com · agy · Google Cloud · ~/.claude · ~/.codex · remote host (SSH)
                              |
             fetch_one.sh <source>   (each source runs independently, in parallel)
                              |  atomic write, file lock
                       ~/.ai_usage_cache.json
                        |                     |
              AIUsage.app (SwiftUI)    AIUsageWidget (WidgetKit)
```

The app and the widget only read the cache. The refresh button only writes a trigger file and a LaunchAgent does the rest.

## Components

- **`fetch_one.sh`** takes one source (`claude`, `codex`, `codexhist`, `agy`, `gemini`, `insights`) and merges the result into the cache. Parser errors go to a log file instead of being swallowed.
- **`ClaudeHelper`** is a small WKWebView app that keeps the sign-in session and prints the page content for the scripts to parse. Its modes are `login`, `fetch`, `login-aistudio`, `fetch-aistudio`, `login-console` and `fetch-console`.
- **`remote_insights.py`** runs on the remote host, reads the session logs incrementally and returns compressed JSON.
- **`Shared.swift`** is compiled into both the app and the widget: the logo parser, the data model and the shared forecast logic.

## Data rules

- Each source writes to its own key in the cache with a `fetched_at`. The `_fresh` flag marks a genuinely successful fetch; only then does the shared clock advance.
- A transient fetch failure keeps the previous numbers and never overwrites them with empty values.
- When a session expires the old numbers are kept and `needs_login` is set so the card shows a sign-in button.

## Pitfalls met

- **The helper's background window must be on screen.** An off-screen window makes WebKit report `visibilityState=hidden`, parks `requestAnimationFrame`, and Next.js pages such as the Anthropic Console never render. The helper uses a 99% transparent window placed on screen that ignores the mouse.
- **Cookies are tied to the helper's bundle id.** A loose executable run outside the `.app` bundle does not see the sign-in session.
- **The claude.ai usage page** has a button whose label ends in an icon glyph, so a regular expression must not anchor on `$`; the Console text also contains icon glyphs on their own lines between a label and its number.
- **The widget extension process survives for a long time** across deploys. After every deploy that changes the widget, terminate the `AIUsageWidgetExtension` process so the system starts the new build.
- **A broken build copied by mistake.** Never pipe `xcodebuild` through `grep` and skip checking for `BUILD SUCCEEDED`.
- **Ad-hoc signing inside a synced folder** fails with "detritus not allowed"; sign a copy in a temporary directory.
- **The widget has no real `NSHomeDirectory()`** because it runs in a sandbox; the cache path is built from the user name.

## Illustrations

The app has two environment variables for producing promotional screenshots: `AIUSAGE_HOME` points the cache and scripts at a folder of sample data, and `AIUSAGE_APPEARANCE` (`light` or `dark`) pins the appearance. This way public images never contain real figures.
