Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .claude/skills/pr-screenshots/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Visual changes benefit enormously from a screenshot — reviewers react to the a
```
![preview](https://raw.githubusercontent.com/<owner>/<repo>/<commit-sha>/.pr-images/<slug>/<name>.png)
```

5. **Followup commit** `git rm`s the image and pushes. GitHub still serves the blob from the named commit, so the SHA-pinned reference keeps working.

## Critical gotcha: pin to the commit SHA, never the branch
Expand Down
62 changes: 31 additions & 31 deletions .claude/skills/prerender-sizing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ allowed-tools: Read, Grep, Glob, Bash

The prerender pool's tab capacity is governed by a small set of SSM-driven knobs:

| Env var | What it controls |
|---|---|
| `PRERENDER_PAGE_POOL_MIN` | Idle floor — pool never contracts below this. |
| `PRERENDER_PAGE_POOL_MAX` | Burst ceiling reachable by any priority. |
| `PRERENDER_PAGE_POOL_HIGH_PRIORITY_MAX` | Extra ceiling, reachable only when caller `priority >= HIGH_PRIORITY_THRESHOLD`. |
| `PRERENDER_HIGH_PRIORITY_THRESHOLD` | Priority bar that unlocks the upper tier. |
| `PRERENDER_PAGE_POOL_IDLE_CONTRACTION_MS` | Hysteresis window before each contraction tick. |
| `PRERENDER_SHARED_CONTEXT_CAP` | Absolute LRU cap for cached BrowserContexts. |
| Env var | What it controls |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| `PRERENDER_PAGE_POOL_MIN` | Idle floor — pool never contracts below this. |
| `PRERENDER_PAGE_POOL_MAX` | Burst ceiling reachable by any priority. |
| `PRERENDER_PAGE_POOL_HIGH_PRIORITY_MAX` | Extra ceiling, reachable only when caller `priority >= HIGH_PRIORITY_THRESHOLD`. |
| `PRERENDER_HIGH_PRIORITY_THRESHOLD` | Priority bar that unlocks the upper tier. |
| `PRERENDER_PAGE_POOL_IDLE_CONTRACTION_MS` | Hysteresis window before each contraction tick. |
| `PRERENDER_SHARED_CONTEXT_CAP` | Absolute LRU cap for cached BrowserContexts. |

Plus the ECS task definition's `cpu` and `memory`. All these together form the **memory envelope** that bounds how many warmed BrowserContexts the system can hold and how much burst headroom it has.

Expand All @@ -31,7 +31,7 @@ Trigger on any of:
- "Why does the dashboard show prerender memory peak at X%?"
- "Should I bump `PRERENDER_PAGE_POOL_MAX` from N to M?"

If the user is asking "why did this single render time out", that's the `indexing-diagnostics` skill, not this one. This skill is for *capacity planning*.
If the user is asking "why did this single render time out", that's the `indexing-diagnostics` skill, not this one. This skill is for _capacity planning_.

## The sizing model

Expand All @@ -47,7 +47,7 @@ where:
- `N`: number of warmed pool entries (active tabs + standby contexts the LRU is holding).
- `marginal_per_tab`: cost of one additional warmed BrowserContext + its cached fetches + tab queue state. Empirically derived per environment.

**CPU follows a different shape.** Each *actively rendering* tab consumes approximately one busy CPU core (Chromium docs / observed). But tabs alternate between rendering, host-side waits (fetches, store loads), and idle. So:
**CPU follows a different shape.** Each _actively rendering_ tab consumes approximately one busy CPU core (Chromium docs / observed). But tabs alternate between rendering, host-side waits (fetches, store loads), and idle. So:

```
cpu_peak ≈ (# tabs rendering simultaneously) × 1 vCPU
Expand Down Expand Up @@ -148,7 +148,7 @@ Confirms whether the system held under pressure (zero render-timeouts) or was at
-- skip the malformed rows: e.g. `AND diagnostics->'waits' ?
-- 'tabQueueMs'` (the JSONB `?` operator tests for a key) keeps
-- only rows with that key present.
SELECT
SELECT
count(*) AS rows_with_diag,
count(*) FILTER (WHERE (diagnostics->>'totalElapsedMs')::int >= 145000) AS at_or_over_timeout,
percentile_cont(0.95) WITHIN GROUP (ORDER BY (diagnostics->>'totalElapsedMs')::int) AS p95_total_ms,
Expand All @@ -167,7 +167,7 @@ WHERE diagnostics IS NOT NULL

Key signals to look for:

- `at_or_over_timeout > 0`: the system is *already* dropping renders. Sizing change is needed urgently.
- `at_or_over_timeout > 0`: the system is _already_ dropping renders. Sizing change is needed urgently.
- `max_tabq_ms` of seconds-to-tens-of-seconds: the user was waiting for a tab. This is the UX-visible pressure that priority routing + dynamic expansion exists to mitigate.
- `max_sem_ms` of seconds-to-tens-of-seconds: global render-semaphore saturation. Indicates pool is too small or fleet is too small.
- `p99_total_ms` near `145000` (the timeout budget): system was at the edge. Even if no timeouts fired, you're one bad burst from a 504.
Expand Down Expand Up @@ -230,13 +230,13 @@ If the resize affects task size, do a Fargate pricing comparison. us-east-1 on-d

So:

| Task size | $/hr | /month per task |
|---|---:|---:|
| 1 vCPU / 4 GB | $0.058 | $42 |
| 2 vCPU / 8 GB | $0.117 | $85 |
| 2 vCPU / 16 GB | $0.152 | $111 |
| 4 vCPU / 8 GB | $0.197 | $144 |
| 4 vCPU / 16 GB | $0.233 | $170 |
| Task size | $/hr | /month per task |
| -------------- | -----: | --------------: |
| 1 vCPU / 4 GB | $0.058 | $42 |
| 2 vCPU / 8 GB | $0.117 | $85 |
| 2 vCPU / 16 GB | $0.152 | $111 |
| 4 vCPU / 8 GB | $0.197 | $144 |
| 4 vCPU / 16 GB | $0.233 | $170 |

If the resize is "swap memory for CPU" (the typical case for prerender — memory-bound, CPU over-provisioned), the cost may actually drop. **Always show the pricing delta in the PR description.** It's a meaningful data point for the resize decision.

Expand All @@ -246,12 +246,12 @@ Captured on 2026-04-30 ~20:00 UTC for the CS-10976 PR 12 staging activation.

### Telemetry

| Metric | 24 h | 7 d |
|---|---:|---:|
| CPU avg of 5-min Avg | 1.1 % | 1.5 % |
| CPU 5-min peak | 67.5 % | 97.5 % |
| Memory avg of 5-min Avg | 35 % | 39 % |
| Memory 5-min peak | 64 % | 98.3 % |
| Metric | 24 h | 7 d |
| ----------------------- | -----: | -----: |
| CPU avg of 5-min Avg | 1.1 % | 1.5 % |
| CPU 5-min peak | 67.5 % | 97.5 % |
| Memory avg of 5-min Avg | 35 % | 39 % |
| Memory 5-min peak | 64 % | 98.3 % |

7-d render-timing histogram from `boxel_index.diagnostics`:

Expand Down Expand Up @@ -285,12 +285,12 @@ Queue-snapshot at the memory peak:

### Memory projection

| N tabs | Memory used | 8 GB (today) | 16 GB (resized) |
|---:|---:|:---:|:---:|
| 2 (MIN) | 3.7 GB | 46 % ✓ | 23 % ✓ |
| 4 | 5.4 GB | 67 % ✓ | 34 % ✓ |
| **6 (MAX)** | **7.1 GB** | **89 % ✗** | **44 % ✓** |
| **8 (HP_MAX)** | **8.7 GB** | **109 % ✗ OOM** | **55 % ✓** |
| N tabs | Memory used | 8 GB (today) | 16 GB (resized) |
| -------------: | ----------: | :-------------: | :-------------: |
| 2 (MIN) | 3.7 GB | 46 % ✓ | 23 % ✓ |
| 4 | 5.4 GB | 67 % ✓ | 34 % ✓ |
| **6 (MAX)** | **7.1 GB** | **89 % ✗** | **44 % ✓** |
| **8 (HP_MAX)** | **8.7 GB** | **109 % ✗ OOM** | **55 % ✓** |

The 16 GB resize is what makes HP_MAX=8 safe. On the existing 8 GB task, MAX=6 is already tight; HP_MAX=8 would OOM.

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Restore boxel-icons build cache
id: icons-cache
uses: ./.github/actions/restore-icons-cache
- name: Build boxel-icons and boxel-ui
- name: Build boxel-icons
run: mise run build:ui
env:
SKIP_ICONS_BUILD: ${{ steps.icons-cache.outputs.cache-hit }}
Expand Down
11 changes: 1 addition & 10 deletions .github/workflows/ci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,7 @@ jobs:
- name: Lint Boxel UI
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/boxel-ui/addon
- name: Build Boxel UI
# To faciliate linting of projects that depend on Boxel UI
if: ${{ !cancelled() }}
run: pnpm run build
working-directory: packages/boxel-ui/addon
- name: Lint Boxel UI Test App
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/boxel-ui/test-app
working-directory: packages/boxel-ui
- name: Lint Host
if: ${{ !cancelled() }}
run: pnpm run lint
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,13 @@ jobs:
- name: Restore boxel-icons build cache
id: icons-cache
uses: ./.github/actions/restore-icons-cache
- name: Build boxel-icons and boxel-ui
- name: Build boxel-icons
run: mise run build:ui
env:
SKIP_ICONS_BUILD: ${{ steps.icons-cache.outputs.cache-hit }}
- name: Run test suite
run: pnpm test
working-directory: packages/boxel-ui/test-app
working-directory: packages/boxel-ui

boxel-ui-raw-icon-changes-only:
name: Boxel UI ensure raw icon changes only
Expand All @@ -344,7 +344,7 @@ jobs:
- uses: ./.github/actions/init
- name: Rebuild boxel-ui icons
run: pnpm rebuild:icons
working-directory: packages/boxel-ui/addon
working-directory: packages/boxel-ui
- name: Fail if generated icons have been changed without underlying raw icon changing
run: git diff --exit-code

Expand Down Expand Up @@ -837,9 +837,6 @@ jobs:
- name: Build Boxel Icons
run: pnpm run build
working-directory: packages/boxel-icons
- name: Build Boxel UI
run: pnpm run build
working-directory: packages/boxel-ui/addon
- name: Build host dist
run: pnpm build
working-directory: packages/host
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Restore boxel-icons build cache
id: icons-cache
uses: ./.github/actions/restore-icons-cache
- name: Build boxel-icons and boxel-ui
- name: Build boxel-icons
run: mise run build:ui
env:
SKIP_ICONS_BUILD: ${{ steps.icons-cache.outputs.cache-hit }}
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/test-web-assets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ jobs:
key: ${{ steps.keys.outputs.cache_key }}
path: |
packages/boxel-icons/dist
packages/boxel-ui/addon/dist
packages/host/dist
.ci/test-web-assets/manifest.json

Expand Down Expand Up @@ -133,7 +132,6 @@ jobs:
key: ${{ steps.keys.outputs.cache_key }}
path: |
packages/boxel-icons/dist
packages/boxel-ui/addon/dist
packages/host/dist
.ci/test-web-assets/manifest.json

Expand All @@ -143,7 +141,6 @@ jobs:
name: ${{ steps.keys.outputs.artifact_name }}
path: |
packages/boxel-icons/dist
packages/boxel-ui/addon/dist
packages/host/dist
.ci/test-web-assets/manifest.json
retention-days: 7
11 changes: 5 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@

- No tests

### packages/boxel-ui/addon, packages/boxel-ui/test-app
### packages/boxel-ui

- Addon functionality is tested via sibling test-app directory
- `cd packages/boxel-ui/addon && pnpm start` to start a process that will watch files and automatically rebuild the addon
- `cd packages/boxel-ui/test-app && pnpm start` to start a process that will watch files and automatically rebuild the test-app
- Addon functionality with in-package test suite
- `cd packages/boxel-ui && pnpm start` to start development server
- Run all tests
`cd packages/boxel-ui/test-app && ember test --path dist`
`cd packages/boxel-ui && ember test --path dist`
- To run a subset of the tests:
`ember test --path dist --filter "some text that appears in module name or test name"`
Note that the filter is matched against the module name and test name, not the file name! Try to avoid using pipe characters in the filter, since they can confuse auto-approval tool use filters set up by the user.
Expand Down Expand Up @@ -92,7 +91,7 @@
#### CSS Guidance

- Use scalable units such as rem
- Use CSS variables from packages/boxel-ui/addon/src/styles/variables.css
- Use CSS variables from packages/boxel-ui/src/styles/variables.css

#### Iterating on host tests with the Chrome MCP server

Expand Down
32 changes: 12 additions & 20 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,21 @@ To build the entire repository and run the application, follow these steps:
pnpm install
```

4. Build the boxel-ui addon:

```zsh
cd ./packages/boxel-ui/addon
pnpm rebuild:icons
pnpm build
```

5. Build the boxel-icons:
4. Build the boxel-icons:

```zsh
cd ./packages/boxel-icons
pnpm build
```

6. Build the host:
5. Build the host:

```zsh
cd ./packages/host
pnpm start
```

7. Run the realm server:
6. Run the realm server:

```zsh
cd ./packages/realm-server
Expand All @@ -66,14 +58,14 @@ To build the entire repository and run the application, follow these steps:
})
```

8. Register ALL:
7. Register ALL:

```zsh
cd ./packages/matrix
pnpm register-all
```

9. Verify registration:
8. Verify registration:

```zsh
cd ./packages/matrix
Expand All @@ -82,20 +74,20 @@ To build the entire repository and run the application, follow these steps:

Visit http://localhost:8080. Type in Username = "admin", Password: "password" Homeserver URL: http://localhost:8008

10. Host App
- Visit https://localhost:4200/
- Enter the registration flow and create a Boxel Account
- When prompted for an authentication token, type in "dev-token"
9. Host App
- Visit https://localhost:4200/
- Enter the registration flow and create a Boxel Account
- When prompted for an authentication token, type in "dev-token"

11. Validate email for login
10. Validate email for login
- Visit SMTP UI at http://localhost:5001/
- Validate email
- Go back to Host https://localhost:4200/ and login

12. Perform "Setup up Secure Payment Method" flow
11. Perform "Setup up Secure Payment Method" flow
- More detailed steps can be found in our [README](README.md) Payment Setup section

13. Run ai bot (Optional):
12. Run ai bot (Optional):

```zsh
cd ./packages/ai-bot
Expand Down
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ For a quickstart, see [here](./QUICKSTART.md)

`packages/realm-server` is a node app that serves the realm as an HTTP server, as well as, it can also host the runtime application for its own realm.

`packages/boxel-ui/addon` is the UI components Ember addon

`packages/boxel-ui/test-app` is the test suite and component explorer for boxel-ui, deployed at [boxel-ui.stack.cards](https://boxel-ui.stack.cards)
`packages/boxel-ui` is the UI components Ember addon. The package also includes a test suite and component explorer, deployed at [boxel-ui.stack.cards](https://boxel-ui.stack.cards)

`packages/matrix` is the docker container for running the matrix server: synapse, as well as tests that involve running a matrix client.

Expand Down Expand Up @@ -109,9 +107,8 @@ Make sure that you have created a matrix user for the base and experiments realm

In order to run the ember-cli hosted app:

1. `pnpm build` in the boxel-ui/addon workspace to build the boxel-ui addon.
2. `pnpm start` in the host/ workspace to serve the ember app.
3. `mise run dev` from the repo root to serve the base and experiments realms -- this will also allow you to switch between the app and the tests without having to restart servers). This expects the Ember application to be running at `https://localhost:4200`, if you’re running it elsewhere you can specify it with `HOST_URL=http://localhost:5200 mise run dev`.
1. `pnpm start` in the host/ workspace to serve the ember app.
2. `mise run dev` from the repo root to serve the base and experiments realms -- this will also allow you to switch between the app and the tests without having to restart servers). This expects the Ember application to be running at `https://localhost:4200`, if you’re running it elsewhere you can specify it with `HOST_URL=http://localhost:5200 mise run dev`.

Alternatively, you can run everything with a single command from the repo root:

Expand Down
Loading
Loading