diff --git a/.claude/skills/indexing-diagnostics/SKILL.md b/.claude/skills/indexing-diagnostics/SKILL.md index 3a1658e86d..53cc1e7c6a 100644 --- a/.claude/skills/indexing-diagnostics/SKILL.md +++ b/.claude/skills/indexing-diagnostics/SKILL.md @@ -868,12 +868,12 @@ When the summary signals (Mode A's `cpuTopFrames`, Mode D) name a hot or looping ### Two tiers — pick by what you need -| Tier | What | Where it lands | Captures the hard wedge? | -|------|------|----------------|--------------------------| -| 1 (always on for targeted realms) | Top-N self-time **summary** | `prerenderer` log: `affinity CPU profile …` | No — `Profiler.stop` needs the renderer thread | -| 2 — `.cpuprofile` | Full V8 CPU profile (whole call tree) | S3 `…/.cpuprofile` | No — same `Profiler.stop` limit | -| 2 — trace (`.trace.json`) | CDP/Perfetto trace, **streamed** — separates JS / GC / compile / layout / paint | S3 `…/.trace.json` | **Yes** — buffered on browser threads, drained out-of-band; the one capture that survives a fully-pegged renderer | -| 2 — heap (`.heapprofile`) | Cumulative allocation-sampling profile, flushed per render | S3 `…/.heapprofile` | No — `getSamplingProfile` needs the renderer thread | +| Tier | What | Where it lands | Captures the hard wedge? | +| --------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| 1 (always on for targeted realms) | Top-N self-time **summary** | `prerenderer` log: `affinity CPU profile …` | No — `Profiler.stop` needs the renderer thread | +| 2 — `.cpuprofile` | Full V8 CPU profile (whole call tree) | S3 `…/.cpuprofile` | No — same `Profiler.stop` limit | +| 2 — trace (`.trace.json`) | CDP/Perfetto trace, **streamed** — separates JS / GC / compile / layout / paint | S3 `…/.trace.json` | **Yes** — buffered on browser threads, drained out-of-band; the one capture that survives a fully-pegged renderer | +| 2 — heap (`.heapprofile`) | Cumulative allocation-sampling profile, flushed per render | S3 `…/.heapprofile` | No — `getSamplingProfile` needs the renderer thread | Rule of thumb: a render that **completes but is heavy** → `.cpuprofile` (+ heap for allocation growth). A render that **fully wedges** (no `cpuTopFrames`, `scriptBusy=`) → the **trace**, which is the only thing that comes back. If the trace returns idle (no hot frame), the wedge isn't CPU-spinning — pivot to "what is it blocked on" (Mode A's `pendingFetches`). @@ -881,17 +881,18 @@ Rule of thumb: a render that **completes but is heavy** → `.cpuprofile` (+ hea All live at `//boxel/` (Systems Manager → Parameter Store). The bucket itself (`PRERENDER_ARTIFACTS_BUCKET`) and the key prefix (`PRERENDER_ARTIFACTS_ENV`) are wired by Terraform — don't set them by hand. -| Parameter | Values | Effect | -|-----------|--------|--------| -| `PRERENDER_PROFILE_AFFINITY` | comma-separated affinity keys, e.g. `realm:https://realms.cardstack.com/team/foo/` | **Required to target.** Only renders whose affinity key exactly matches are profiled at all (Tier 1 + Tier 2). Empty / `off` → everything inert. | -| `PRERENDER_PROFILE_CPUPROFILE` | `true` / `false` | Persist the full `.cpuprofile` for targeted renders. | -| `PRERENDER_PROFILE_TRACE` | `true` / `false` | Capture the streaming trace for targeted renders. | -| `PRERENDER_PROFILE_HEAP` | `true` / `false` | Capture the heap allocation-sampling profile for targeted renders. | -| `PRERENDER_PROFILE_MAX_SESSION_BYTES` | positive integer, or `0` for the default | Soft per-process byte budget across all artifacts. `0`/unset → 5 GiB. Once spent, the task declines further uploads (in-flight ones finish, so blobs are never truncated). | +| Parameter | Values | Effect | +| ------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PRERENDER_PROFILE_AFFINITY` | comma-separated affinity keys, e.g. `realm:https://realms.cardstack.com/team/foo/` | **Required to target.** Only renders whose affinity key exactly matches are profiled at all (Tier 1 + Tier 2). Empty / `off` → everything inert. | +| `PRERENDER_PROFILE_CPUPROFILE` | `true` / `false` | Persist the full `.cpuprofile` for targeted renders. | +| `PRERENDER_PROFILE_TRACE` | `true` / `false` | Capture the streaming trace for targeted renders. | +| `PRERENDER_PROFILE_HEAP` | `true` / `false` | Capture the heap allocation-sampling profile for targeted renders. | +| `PRERENDER_PROFILE_MAX_SESSION_BYTES` | positive integer, or `0` for the default | Soft per-process byte budget across all artifacts. `0`/unset → 5 GiB. Once spent, the task declines further uploads (in-flight ones finish, so blobs are never truncated). | The mode flags are Terraform-seeded sentinels (default `false` / `0`); `PRERENDER_PROFILE_AFFINITY` is operator-managed and must already exist. The affinity key is `realm:` + the realm's canonical URL **with trailing slash** — the same value Mode A/B logs print as `affinity=…`. > **The container reads these at task start.** ECS injects SSM values when a task launches, so a change only takes effect on a fresh task. After editing the parameters, force a new deployment of the prerender service so tasks restart with the new env: +> > ``` > aws ecs update-service --cluster --service boxel-prerender-server- --force-new-deployment > ``` @@ -900,7 +901,7 @@ The mode flags are Terraform-seeded sentinels (default `false` / `0`); `PRERENDE 1. **Target the realm.** Set `PRERENDER_PROFILE_AFFINITY` to its affinity key and turn on the mode flag(s) you need (start with `PRERENDER_PROFILE_TRACE` for a wedge, `PRERENDER_PROFILE_CPUPROFILE` for a heavy-but-completing render). 2. **Restart the service** (`--force-new-deployment` above) so the tasks pick up the values. -3. **Generate renders.** Trigger a reindex of the targeted realm (see *Triggering a reindex* below) — the indexer's per-card visits are what produce artifacts. Confirm captures are happening in the `prerenderer` log: `artifact-sink uploaded key=… bytes=… sessionBytes=…/…`. +3. **Generate renders.** Trigger a reindex of the targeted realm (see _Triggering a reindex_ below) — the indexer's per-card visits are what produce artifacts. Confirm captures are happening in the `prerenderer` log: `artifact-sink uploaded key=… bytes=… sessionBytes=…/…`. 4. **Pull the artifacts** (below). 5. **Turn it off.** Set the mode flags back to `false` (and clear `PRERENDER_PROFILE_AFFINITY` if done), then force one more deployment. Leftover artifacts auto-expire after 14 days regardless. @@ -917,14 +918,14 @@ The key schema is `env/realm/jobId/card/step/-.` — eve ### Reading each artifact -- **`.cpuprofile`** — Chrome DevTools (Performance panel → *Load profile…*) or [speedscope](https://www.speedscope.app/). Self-time flame graph of the whole render; the summary's top frames are just the peak of this. -- **`.trace.json`** — [Perfetto UI](https://ui.perfetto.dev/) or Chrome DevTools Performance → *Load profile…*. Separate tracks for JS execution, V8 GC, compile, and layout/paint — this is how you tell a JS spin (`v8.execute` saturated) from GC thrash (`v8.gc` saturated) when the summary couldn't say. -- **`.heapprofile`** — Chrome DevTools Memory → *Allocation sampling* → *Load profile…*. Each upload is the cumulative profile **at that render**, so download two from different points in the session and compare to see which call sites kept allocating. +- **`.cpuprofile`** — Chrome DevTools (Performance panel → _Load profile…_) or [speedscope](https://www.speedscope.app/). Self-time flame graph of the whole render; the summary's top frames are just the peak of this. +- **`.trace.json`** — [Perfetto UI](https://ui.perfetto.dev/) or Chrome DevTools Performance → _Load profile…_. Separate tracks for JS execution, V8 GC, compile, and layout/paint — this is how you tell a JS spin (`v8.execute` saturated) from GC thrash (`v8.gc` saturated) when the summary couldn't say. +- **`.heapprofile`** — Chrome DevTools Memory → _Allocation sampling_ → _Load profile…_. Each upload is the cumulative profile **at that render**, so download two from different points in the session and compare to see which call sites kept allocating. ### What Mode H can't tell you - The `.cpuprofile` and `.heapprofile` need the renderer thread to serialize, so a **fully-wedged** render produces neither — only the streaming trace comes back. That's by design (Tier 1's summary has the same limit); the trace is the wedge tool. -- Browser-wide tracing is **single-flight** — only one trace runs at a time across the whole pool. Concurrent targeted renders skip their trace (logged at `debug`), so don't expect a trace for *every* render under load; constrain concurrency or accept the gaps. The summary and the cpuprofile/heap captures are per-render and unaffected. +- Browser-wide tracing is **single-flight** — only one trace runs at a time across the whole pool. Concurrent targeted renders skip their trace (logged at `debug`), so don't expect a trace for _every_ render under load; constrain concurrency or accept the gaps. The summary and the cpuprofile/heap captures are per-render and unaffected. - Captured artifacts are anonymized only at the key level (host stripped). The blobs themselves contain card URLs and code paths — treat them as you would any prerender diagnostic. ## Field-by-field reading diff --git a/.claude/skills/pr-screenshots/SKILL.md b/.claude/skills/pr-screenshots/SKILL.md index a04cfd42b6..9392f4929d 100644 --- a/.claude/skills/pr-screenshots/SKILL.md +++ b/.claude/skills/pr-screenshots/SKILL.md @@ -23,6 +23,7 @@ Visual changes benefit enormously from a screenshot — reviewers react to the a ``` ![preview](https://raw.githubusercontent.com////.pr-images//.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 diff --git a/.claude/skills/prerender-sizing/SKILL.md b/.claude/skills/prerender-sizing/SKILL.md index 28263eed15..e0fb0380e4 100644 --- a/.claude/skills/prerender-sizing/SKILL.md +++ b/.claude/skills/prerender-sizing/SKILL.md @@ -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. @@ -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 @@ -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 @@ -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, @@ -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. @@ -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. @@ -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`: @@ -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. diff --git a/.github/workflows/build-host.yml b/.github/workflows/build-host.yml index 00b47eeca1..a8b60b7afa 100644 --- a/.github/workflows/build-host.yml +++ b/.github/workflows/build-host.yml @@ -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 }} diff --git a/.github/workflows/ci-lint.yaml b/.github/workflows/ci-lint.yaml index e5790f8554..e8d4ce831a 100644 --- a/.github/workflows/ci-lint.yaml +++ b/.github/workflows/ci-lint.yaml @@ -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 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4fd62766b6..2fa9f45422 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -311,13 +311,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 @@ -332,7 +332,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 @@ -804,9 +804,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 diff --git a/.github/workflows/deploy-ui.yml b/.github/workflows/deploy-ui.yml index 348c0bb3b6..0f34de3974 100644 --- a/.github/workflows/deploy-ui.yml +++ b/.github/workflows/deploy-ui.yml @@ -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 }} diff --git a/.github/workflows/test-web-assets.yaml b/.github/workflows/test-web-assets.yaml index 133e22cf8b..aa79719992 100644 --- a/.github/workflows/test-web-assets.yaml +++ b/.github/workflows/test-web-assets.yaml @@ -58,7 +58,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 @@ -110,7 +109,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 @@ -120,7 +118,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 diff --git a/AGENTS.md b/AGENTS.md index 86a7eb2c88..f374d88bb0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. @@ -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 diff --git a/QUICKSTART.md b/QUICKSTART.md index 8ec2437d48..7825415540 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -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 @@ -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 @@ -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 diff --git a/README.md b/README.md index 752ae6f599..bf0e18afa8 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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: diff --git a/docs/aws-operations.md b/docs/aws-operations.md index 6d21dcbd61..38e4255a47 100644 --- a/docs/aws-operations.md +++ b/docs/aws-operations.md @@ -2,7 +2,7 @@ Operational runbooks for boxel services on AWS. Each section is a self-contained procedure: pre-requisites, the steps to run, what to verify after, and how to roll back. -These procedures assume you already have AWS access set up — the `aws-access` claude skill walks new teammates through the one-time setup. Most procedures here need an AWS profile with `ssm:PutParameter` / `ecs:UpdateService` etc. on the target prefix; the read-only `claude-staging` / `claude-prod` profiles from that skill are *not* sufficient. +These procedures assume you already have AWS access set up — the `aws-access` claude skill walks new teammates through the one-time setup. Most procedures here need an AWS profile with `ssm:PutParameter` / `ecs:UpdateService` etc. on the target prefix; the read-only `claude-staging` / `claude-prod` profiles from that skill are _not_ sufficient. Conventions used throughout: @@ -68,17 +68,20 @@ This rolls the running tasks one at a time; expect a 1–2 minute window where s ### Validation gate Before promoting to prod, run a synthetic saturating workload on staging: + - Concurrent catalog full reindex (priority 0) - Simulated user-driven incremental reindex on a different realm (priority 10) - Both for ~5 minutes **Pass criteria:** + - High-priority p95 `tabQueueMs` < 1 s during the burst. - Memory peak < 80 % of allocated. - CPU peak < 80 % of allocated, sustained over a 1-minute window (brief overshoots OK). - Zero 145-second render-timeouts (`SELECT count(*) FROM boxel_index WHERE (diagnostics->>'totalElapsedMs')::int >= 145000`). **Adjustment paths:** + - CPU peak > 80 % sustained → drop `MAX` by 1, retest. - Memory peak > 80 % → drop `HIGH_PRIORITY_MAX` by 1, retest. - Tab-queue wait spike at high priority → investigate manager-side routing; the priority-aware `scoreCandidate` should be picking the right server. @@ -87,14 +90,14 @@ Before promoting to prod, run a synthetic saturating workload on staging: If the ECS task is still 4 vCPU / 8 GB, the recommended values would OOM at `HP_MAX = 8` (memory model: 8 × 836 MB + 2 GB ≈ 8.7 GB → 109 % of 8 GB). Use these instead — modest improvement over today's behaviour, no infra change required: -| Knob | 16 GB task (recommended) | 8 GB task (fallback) | -|---|---:|---:| -| `PRERENDER_PAGE_POOL_MIN` | 2 | 2 | -| `PRERENDER_PAGE_POOL_MAX` | 6 | 4 | -| `PRERENDER_PAGE_POOL_HIGH_PRIORITY_MAX` | 8 | 5 | -| `PRERENDER_HIGH_PRIORITY_THRESHOLD` | 5 | 5 | -| `PRERENDER_POOL_IDLE_CONTRACTION_MS` | 60000 | 60000 | -| `PRERENDER_SHARED_CONTEXT_CAP` | 12 | 8 | +| Knob | 16 GB task (recommended) | 8 GB task (fallback) | +| --------------------------------------- | -----------------------: | -------------------: | +| `PRERENDER_PAGE_POOL_MIN` | 2 | 2 | +| `PRERENDER_PAGE_POOL_MAX` | 6 | 4 | +| `PRERENDER_PAGE_POOL_HIGH_PRIORITY_MAX` | 8 | 5 | +| `PRERENDER_HIGH_PRIORITY_THRESHOLD` | 5 | 5 | +| `PRERENDER_POOL_IDLE_CONTRACTION_MS` | 60000 | 60000 | +| `PRERENDER_SHARED_CONTEXT_CAP` | 12 | 8 | ### Rollback diff --git a/docs/spec.md b/docs/spec.md index c947f67f6a..1f1fda17a3 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -8,8 +8,8 @@ At its core, a Spec links to an exported definition in modules via **Code Ref** ```typescript { - module: string; // Path to the module file (e.g., './my-component.gts') - name: string; // Export name (e.g., 'MyComponent') + module: string; // Path to the module file (e.g., './my-component.gts') + name: string; // Export name (e.g., 'MyComponent') } ``` @@ -18,21 +18,21 @@ When a module is edited (for example, if a definition is renamed), the spec migh ## Why We Need Specs - **Search for instance creation** - Enable discovery and instantiation of available components. Code refs are not searchable unless wrapped inside a card instance (a spec) which is then indexed and can benefit from all search capabilities. This makes field definitions searchable as well. -- **Packaging of listings** - Code packaging in bundles occurs via linksToMany specs. The installation process looks for modules and code refs to copy during installation. +- **Packaging of listings** - Code packaging in bundles occurs via linksToMany specs. The installation process looks for modules and code refs to copy during installation. - **Link to examples** - Provide interactive demonstrations and usage patterns - **Documentation for code** - Maintain up-to-date, validated documentation connected to implementation ## Different Spec Types -Code categorization is endless, so we focus on a subset of exported code that interests us within the Boxel ecosystem. +Code categorization is endless, so we focus on a subset of exported code that interests us within the Boxel ecosystem. The Boxel ecosystem supports five distinct spec types: -- **`card`** -- **`field`** -- **`component`** -- **`app`** -- **`command`** +- **`card`** +- **`field`** +- **`component`** +- **`app`** +- **`command`** Each spec type has specific characteristics and use cases: @@ -41,12 +41,14 @@ Each spec type has specific characteristics and use cases: **Purpose**: Document card definitions - the ultimate sharing unit of the Boxel ecosystem. **Characteristics**: + - Can have multiple view templates (isolated, fitted, embedded, edit) - Often contain fields and computed properties - May extend other cards through inheritance -- Support `linkedExamples` +- Support `linkedExamples` **Example Use Cases**: + - `Author` - Author profile card with bio and social links - `Contact` - Contact information card for CRM systems - `BlogPost` - Blog post card with content and metadata @@ -57,12 +59,14 @@ Each spec type has specific characteristics and use cases: **Purpose**: Document field definitions used within cards. **Characteristics**: + - Only support `containedExamples` (embedded within the spec) - May be primitive (string, number, boolean) or composite -- TBD: display of primitive contained examples +- TBD: display of primitive contained examples - May extend other fields through inheritance **Example Use Cases**: + - `SocialMediaLink` - Composite field for social platform data - `MaybeBase64Field` - String field with base64 encoding capabilities - `TextAreaField` - Multi-line text input field @@ -73,12 +77,14 @@ Each spec type has specific characteristics and use cases: **Purpose**: Document reusable UI components that don't represent data, so AI agents and developers can discover them by searching the catalog instead of needing a per-component skill. **Characteristics**: + - Only when it extends Glimmer Component - Potentially includes reactive data loading resources from ember-resources - API documentation, an example, and CSS variables live in the `readMe` markdown field - `cardDescription` is the keyword-rich one-liner the agent matches against — keep it concrete (e.g. "Form text input with validation states") rather than abstract **Example Use Cases**: + - `CardsGrid` - Responsive grid layout component for card collections - `FilterDropdown` - Multi-select dropdown component for filtering - `Pill` - Badge component for displaying tags and statuses @@ -150,12 +156,14 @@ the deployed commit. **Purpose**: Document application-level cards that serve as entry points, typically when other cards are queried within them. **Characteristics**: -- Extends AppCard which displays in a wide view -- Often contain more than one query -- Often use navigation components like tabs or sidebars -- Support `linkedExamples` + +- Extends AppCard which displays in a wide view +- Often contain more than one query +- Often use navigation components like tabs or sidebars +- Support `linkedExamples` **Example Use Cases**: + - `BlogApp` - Blog content management system - `PreschoolCRMApp` - Customer relationship management for preschools - `SprintPlanner` - Sprint planning applications @@ -165,11 +173,13 @@ the deployed commit. **Purpose**: Document executable commands within the system. **Characteristics**: -- Represent actions within a command palette + +- Represent actions within a command palette - Imperative code that doesn't depend on data loading in a component -- Have access to host code via boxel-host commands +- Have access to host code via boxel-host commands **Example Use Cases**: + - `GenerateReadmeSpecCommand` - Generate documentation for spec cards - `SearchCardsByQueryCommand` - Advanced card search with filtering - `PatchCardInstanceCommand` - Update card instance data diff --git a/mise-tasks/build/ui b/mise-tasks/build/ui index fe6717b134..a4de92c9d1 100755 --- a/mise-tasks/build/ui +++ b/mise-tasks/build/ui @@ -1,5 +1,5 @@ #!/bin/sh -#MISE description="Build boxel-icons and boxel-ui (in dependency order)" +#MISE description="Build boxel-icons (in dependency order)" set -eu @@ -9,4 +9,3 @@ else pnpm --dir packages/boxel-icons build fi -pnpm --dir packages/boxel-ui/addon build diff --git a/mise-tasks/build/worktree-types b/mise-tasks/build/worktree-types index 90a1df61b2..88d7eafa22 100755 --- a/mise-tasks/build/worktree-types +++ b/mise-tasks/build/worktree-types @@ -6,7 +6,4 @@ set -eu echo "Building @cardstack/boxel-icons..." pnpm --dir packages/boxel-icons build -echo "Building @cardstack/boxel-ui type declarations..." -pnpm --dir packages/boxel-ui/addon build:types - echo "Done — worktree type declarations are ready for pnpm lint" diff --git a/package.json b/package.json index 48ade5c614..28f474b28e 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,14 @@ }, "scripts": { "build:boxel-host": "pnpm run build-common-deps && cd packages/host && NODE_OPTIONS='--max_old_space_size=8192' pnpm build:production", - "build-common-deps": "cd packages/boxel-icons && pnpm build && cd ../.. && cd packages/boxel-ui/addon && pnpm build && cd ../../..", + "build-common-deps": "cd packages/boxel-icons && pnpm build && cd ../..", "ci:failures": "node ./scripts/ci-failures.js", "clear-caches": "find . -name 'node_modules' -type d -prune -exec rm -rf {} \\; && rm -rf $TMPDIR/embroider", "deploy:boxel-host": "pnpm run build-common-deps && cd packages/host && BASE_REALM_HOSTING_DISABLED=true NODE_OPTIONS='--max_old_space_size=8192' pnpm exec ember deploy", "deploy:boxel-host:preview-staging": "pnpm run build-common-deps && cd packages/host && BASE_REALM_HOSTING_DISABLED=true NODE_OPTIONS='--max_old_space_size=8192' pnpm exec ember deploy s3-preview-staging --verbose", "deploy:boxel-host:preview-production": "pnpm run build-common-deps && cd packages/host && BASE_REALM_HOSTING_DISABLED=true NODE_OPTIONS='--max_old_space_size=8192' pnpm exec ember deploy s3-preview-production --verbose", - "deploy:boxel-ui": "pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy", - "deploy:boxel-ui:preview-staging": "pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy s3-preview-staging --verbose", + "deploy:boxel-ui": "pnpm run build-common-deps && cd packages/boxel-ui && pnpm exec ember deploy", + "deploy:boxel-ui:preview-staging": "pnpm run build-common-deps && cd packages/boxel-ui && pnpm exec ember deploy s3-preview-staging --verbose", "lint": "pnpm run --filter './packages/**' --if-present -r lint", "lint:fix": "pnpm run --filter './packages/**' --if-present -r lint:fix", "openrouter:sync": "OPENROUTER_REALM_URL=${OPENROUTER_REALM_URL:-https://localhost:4201/openrouter/} pnpm --filter @cardstack/realm-server sync-openrouter-models", diff --git a/packages/ai-bot/tsconfig.json b/packages/ai-bot/tsconfig.json index 9d1b5b4ebf..24046b18b3 100644 --- a/packages/ai-bot/tsconfig.json +++ b/packages/ai-bot/tsconfig.json @@ -27,8 +27,8 @@ "strict": true, "paths": { "https://cardstack.com/base/*": ["../base/*"], - "@cardstack/boxel-ui": ["../boxel-ui/addon"], - "@cardstack/boxel-ui/*": ["../boxel-ui/addon/*"] + "@cardstack/boxel-ui": ["../boxel-ui"], + "@cardstack/boxel-ui/*": ["../boxel-ui/*"] }, "types": ["@cardstack/local-types"] }, diff --git a/packages/base/tsconfig.json b/packages/base/tsconfig.json index 6dab612795..6cd99d3bd5 100644 --- a/packages/base/tsconfig.json +++ b/packages/base/tsconfig.json @@ -22,15 +22,9 @@ "strict": true, "experimentalDecorators": true, "paths": { - "@cardstack/boxel-ui": [ - "../boxel-ui/addon/declarations" - ], - "@cardstack/boxel-ui/*": [ - "../boxel-ui/addon/declarations/*" - ], - "@cardstack/boxel-host/commands/*": [ - "../host/app/commands/*" - ] + "@cardstack/boxel-ui": ["../boxel-ui"], + "@cardstack/boxel-ui/*": ["../boxel-ui/*"], + "@cardstack/boxel-host/commands/*": ["../host/app/commands/*"] } } } diff --git a/packages/billing/tsconfig.json b/packages/billing/tsconfig.json index 5516357016..785362612e 100644 --- a/packages/billing/tsconfig.json +++ b/packages/billing/tsconfig.json @@ -27,8 +27,8 @@ "strict": true, "paths": { "https://cardstack.com/base/*": ["../base/*"], - "@cardstack/boxel-ui": ["../boxel-ui/addon"], - "@cardstack/boxel-ui/*": ["../boxel-ui/addon/*"], + "@cardstack/boxel-ui": ["../boxel-ui"], + "@cardstack/boxel-ui/*": ["../boxel-ui/*"], "*": ["types/*"] }, "types": ["@cardstack/local-types"] diff --git a/packages/bot-runner/tsconfig.json b/packages/bot-runner/tsconfig.json index eda8b5f44e..ae76348f92 100644 --- a/packages/bot-runner/tsconfig.json +++ b/packages/bot-runner/tsconfig.json @@ -27,8 +27,8 @@ "strict": true, "paths": { "https://cardstack.com/base/*": ["../base/*"], - "@cardstack/boxel-ui": ["../boxel-ui/addon"], - "@cardstack/boxel-ui/*": ["../boxel-ui/addon/*"], + "@cardstack/boxel-ui": ["../boxel-ui"], + "@cardstack/boxel-ui/*": ["../boxel-ui/*"], "@cardstack/runtime-common": ["../runtime-common"], "@cardstack/runtime-common/*": ["../runtime-common/*"], "@cardstack/postgres": ["../postgres"], diff --git a/packages/boxel-cli/plugin/skills/boxel-file-structure/SKILL.md b/packages/boxel-cli/plugin/skills/boxel-file-structure/SKILL.md index 35e73bfb31..ce76c41b3b 100644 --- a/packages/boxel-cli/plugin/skills/boxel-file-structure/SKILL.md +++ b/packages/boxel-cli/plugin/skills/boxel-file-structure/SKILL.md @@ -15,11 +15,11 @@ Example: https://app.boxel.ai/sarah/pet-rescue/animals/dog.gts ## File Naming Conventions -| Type | Convention | Example | -|------|------------|---------| -| Card definitions | `kebab-case.gts` | `blog-post.gts`, `grammy-award.gts` | -| Instance directories | `PascalCase/` | `BlogPost/`, `GrammyAward/` | -| Instance files | `kebab-case.json` | `my-first-post.json` | +| Type | Convention | Example | +| -------------------- | ----------------- | ----------------------------------- | +| Card definitions | `kebab-case.gts` | `blog-post.gts`, `grammy-award.gts` | +| Instance directories | `PascalCase/` | `BlogPost/`, `GrammyAward/` | +| Instance files | `kebab-case.json` | `my-first-post.json` | ## Directory Structure @@ -42,6 +42,7 @@ workspace/ **The `adoptsFrom.module` path is relative to the JSON file location.** ### ✅ Correct: Instance in subdirectory + ``` grammy-award.gts # Definition at root GrammyAward/ # Instances in PascalCase directory @@ -49,11 +50,12 @@ GrammyAward/ # Instances in PascalCase directory ``` **In `GrammyAward/record-of-the-year.json`:** + ```json { "meta": { "adoptsFrom": { - "module": "../grammy-award", // ← Go UP to parent, then to file + "module": "../grammy-award", // ← Go UP to parent, then to file "name": "GrammyAward" } } @@ -61,11 +63,12 @@ GrammyAward/ # Instances in PascalCase directory ``` ### ❌ Wrong: Forgetting the relative path + ```json { "meta": { "adoptsFrom": { - "module": "./grammy-award", // ← WRONG! This looks in GrammyAward/ + "module": "./grammy-award", // ← WRONG! This looks in GrammyAward/ "name": "GrammyAward" } } @@ -74,12 +77,12 @@ GrammyAward/ # Instances in PascalCase directory ## Path Rules Summary -| JSON Location | Definition Location | Module Path | -|--------------|---------------------|-------------| -| `root/Instance.json` | `root/card.gts` | `"./card"` | -| `root/Card/instance.json` | `root/card.gts` | `"../card"` | -| `root/Card/Sub/instance.json` | `root/card.gts` | `"../../card"` | -| `root/Card/instance.json` | `root/other/card.gts` | `"../other/card"` | +| JSON Location | Definition Location | Module Path | +| ----------------------------- | --------------------- | ----------------- | +| `root/Instance.json` | `root/card.gts` | `"./card"` | +| `root/Card/instance.json` | `root/card.gts` | `"../card"` | +| `root/Card/Sub/instance.json` | `root/card.gts` | `"../../card"` | +| `root/Card/instance.json` | `root/other/card.gts` | `"../other/card"` | ## Instance JSON Structure (Full) @@ -138,6 +141,7 @@ GrammyAward/ # Instances in PascalCase directory ``` ### ❌ Wrong: Array syntax (does NOT work) + ```json { "relationships": { @@ -152,21 +156,23 @@ GrammyAward/ # Instances in PascalCase directory ### JSON Structure Rules -| Section | Purpose | Required | -|---------|---------|----------| -| `data.type` | Always `"card"` | Yes | -| `data.attributes` | Scalar field values (string, number, bool) | Yes | -| `data.relationships` | Links to other cards (`linksTo`/`linksToMany`) | Only if has links | -| `data.meta.adoptsFrom` | References the card definition | Yes | +| Section | Purpose | Required | +| ---------------------- | ---------------------------------------------- | ----------------- | +| `data.type` | Always `"card"` | Yes | +| `data.attributes` | Scalar field values (string, number, bool) | Yes | +| `data.relationships` | Links to other cards (`linksTo`/`linksToMany`) | Only if has links | +| `data.meta.adoptsFrom` | References the card definition | Yes | ### Attributes vs Relationships **Use `attributes` for:** + - StringField, NumberField, BooleanField values - FieldDef instances (embedded via `contains`) - Any non-card data **Use `relationships` for:** + - CardDef references (`linksTo` → single link) - CardDef arrays (`linksToMany` → array of links) @@ -174,10 +180,10 @@ GrammyAward/ # Instances in PascalCase directory **🔴 CRITICAL - memorize this:** -| Field Type | Definition uses | Instance uses | -|------------|-----------------|---------------| -| Extends `CardDef` | `linksTo` / `linksToMany` | `relationships` | -| Extends `FieldDef` | `contains` / `containsMany` | `attributes` | +| Field Type | Definition uses | Instance uses | +| ------------------ | --------------------------- | --------------- | +| Extends `CardDef` | `linksTo` / `linksToMany` | `relationships` | +| Extends `FieldDef` | `contains` / `containsMany` | `attributes` | ```gts // In .gts definition: @@ -220,11 +226,13 @@ When linking to other cards, use the card's URL without `.json`: These realms contain shared definitions you can import from: **Production:** + - `https://cardstack.com/base/` - Core types (CardDef, FieldDef, etc.) - `https://app.boxel.ai/catalog/` - Catalog cards - `https://app.boxel.ai/skills/` - Skill cards **Staging:** + - `https://cardstack.com/base/` - Same core types - `https://realms-staging.stack.cards/catalog/` - `https://realms-staging.stack.cards/skills/` @@ -270,6 +278,7 @@ When using the `/_search` API endpoint: ``` **With field filters:** + ```json { "filter": { @@ -283,21 +292,22 @@ When using the `/_search` API endpoint: ## Common Mistakes -| Mistake | Fix | -|---------|-----| -| `"module": "./card"` from subdirectory | Use `"../card"` | -| `contains(CardDef)` | Use `linksTo(CardDef)` | -| `linksTo(FieldDef)` | Use `contains(FieldDef)` | -| Link in `attributes` | Move to `relationships` | -| FieldDef in `relationships` | Move to `attributes` | -| Missing `data` wrapper in JSON | Wrap everything in `{"data": {...}}` | -| PascalCase for `.gts` files | Use `kebab-case.gts` | -| kebab-case for instance dirs | Use `PascalCase/` | -| `linksToMany` as array | Use numbered keys: `field.0`, `field.1`, etc. | +| Mistake | Fix | +| -------------------------------------- | --------------------------------------------- | +| `"module": "./card"` from subdirectory | Use `"../card"` | +| `contains(CardDef)` | Use `linksTo(CardDef)` | +| `linksTo(FieldDef)` | Use `contains(FieldDef)` | +| Link in `attributes` | Move to `relationships` | +| FieldDef in `relationships` | Move to `attributes` | +| Missing `data` wrapper in JSON | Wrap everything in `{"data": {...}}` | +| PascalCase for `.gts` files | Use `kebab-case.gts` | +| kebab-case for instance dirs | Use `PascalCase/` | +| `linksToMany` as array | Use numbered keys: `field.0`, `field.1`, etc. | ## Essential Formats Every CardDef should implement these templates: + - `isolated` - Full detail view (scrollable) - `embedded` - Compact summary for lists - `fitted` - Fixed dimensions for grids/dashboards (CRITICAL for good UX) diff --git a/packages/boxel-cli/plugin/skills/indexing-errors/SKILL.md b/packages/boxel-cli/plugin/skills/indexing-errors/SKILL.md index e2fcb94323..467baabca9 100644 --- a/packages/boxel-cli/plugin/skills/indexing-errors/SKILL.md +++ b/packages/boxel-cli/plugin/skills/indexing-errors/SKILL.md @@ -13,12 +13,12 @@ Use this skill to triage realm health in one call, without reading the database ## When the user asks to... -| Ask | Run | -|---|---| -| "what's failing to index in ?" | `boxel realm indexing-errors --realm ` | -| "which cards have broken links in ?" | `boxel realm indexing-errors --realm --json \| jq '.data[] \| select(.type == "broken-link")'` | -| "give me the full payload as JSON" | `boxel realm indexing-errors --realm --json` | -| "is anything broken in this realm right now?" | `boxel realm indexing-errors --realm ` — exit 0 with "No indexing errors." is the all-clear | +| Ask | Run | +| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| "what's failing to index in ?" | `boxel realm indexing-errors --realm ` | +| "which cards have broken links in ?" | `boxel realm indexing-errors --realm --json \| jq '.data[] \| select(.type == "broken-link")'` | +| "give me the full payload as JSON" | `boxel realm indexing-errors --realm --json` | +| "is anything broken in this realm right now?" | `boxel realm indexing-errors --realm ` — exit 0 with "No indexing errors." is the all-clear | ## Typical sequencing @@ -88,11 +88,19 @@ Each line is `[] `. For `indexing-error` rows t "entryType": "instance", "diagnostics": { "brokenLinks": [ - { "fieldName": "author", "reference": "https://...", "kind": "not-found" } + { + "fieldName": "author", + "reference": "https://...", + "kind": "not-found" + } ] }, "brokenLinks": [ - { "fieldName": "author", "reference": "https://...", "kind": "not-found" } + { + "fieldName": "author", + "reference": "https://...", + "kind": "not-found" + } ] } } diff --git a/packages/boxel-cli/scripts/build-types.ts b/packages/boxel-cli/scripts/build-types.ts index 485ce86d1c..7998d93004 100644 --- a/packages/boxel-cli/scripts/build-types.ts +++ b/packages/boxel-cli/scripts/build-types.ts @@ -34,7 +34,7 @@ * acceptance suites). Used to back * `@cardstack/host/tests/*` imports * in agent `.test.gts` files. - * - `bundled-types/boxel-ui/` — `packages/boxel-ui/addon/src/*` + * - `bundled-types/boxel-ui/` — `packages/boxel-ui/src/*` * minus `styles/` (1.5MB of fonts + * CSS irrelevant to type-checking). * - `bundled-types/local-types/` — `packages/local-types/*.d.ts` @@ -342,9 +342,9 @@ const VENDORS: Vendor[] = [ filter: skipMonorepoArtifacts, }, { - // Drop `addon/src/styles/` (1.5MB of `.woff2` + CSS). + // Drop `src/styles/` (1.5MB of `.woff2` + CSS). name: 'boxel-ui', - from: join(MONOREPO_PACKAGES, 'boxel-ui', 'addon', 'src'), + from: join(MONOREPO_PACKAGES, 'boxel-ui', 'src'), to: join(PACKAGE_ROOT, 'bundled-types', 'boxel-ui'), filter: boxelUiFilter, }, @@ -389,7 +389,7 @@ function hostAppFilter(src: string): boolean { function boxelUiFilter(src: string): boolean { if (!skipMonorepoArtifacts(src)) return false; - let uiRoot = join(MONOREPO_PACKAGES, 'boxel-ui', 'addon', 'src'); + let uiRoot = join(MONOREPO_PACKAGES, 'boxel-ui', 'src'); let rel = src.slice(uiRoot.length + 1); if (rel === '') return true; if (rel.startsWith('styles/') || rel === 'styles') return false; diff --git a/packages/boxel-ui/.editorconfig b/packages/boxel-ui/.editorconfig new file mode 100644 index 0000000000..c35a002406 --- /dev/null +++ b/packages/boxel-ui/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/packages/boxel-ui/.env.development b/packages/boxel-ui/.env.development new file mode 100644 index 0000000000..8380c3a69c --- /dev/null +++ b/packages/boxel-ui/.env.development @@ -0,0 +1,8 @@ +# This file is committed to git and should not contain any secrets. +# +# Vite recommends using .env.local or .env.[mode].local if you need to manage secrets +# SEE: https://vite.dev/guide/env-and-mode.html#env-files for more information. + + +# Default NODE_ENV with vite build --mode=test is production +NODE_ENV=development diff --git a/packages/boxel-ui/addon/.eslintignore b/packages/boxel-ui/.eslintignore similarity index 100% rename from packages/boxel-ui/addon/.eslintignore rename to packages/boxel-ui/.eslintignore diff --git a/packages/boxel-ui/addon/.eslintrc.cjs b/packages/boxel-ui/.eslintrc.cjs similarity index 97% rename from packages/boxel-ui/addon/.eslintrc.cjs rename to packages/boxel-ui/.eslintrc.cjs index 31485b410a..9d90bc0ddc 100644 --- a/packages/boxel-ui/addon/.eslintrc.cjs +++ b/packages/boxel-ui/.eslintrc.cjs @@ -1,6 +1,6 @@ 'use strict'; -const MISSING_INVOKABLES_CONFIG = require('../../runtime-common/etc/eslint/missing-invokables-config'); +const MISSING_INVOKABLES_CONFIG = require('../runtime-common/etc/eslint/missing-invokables-config'); module.exports = { root: true, diff --git a/packages/boxel-ui/.gitignore b/packages/boxel-ui/.gitignore index c9b2f6bae3..0049e30243 100644 --- a/packages/boxel-ui/.gitignore +++ b/packages/boxel-ui/.gitignore @@ -1,15 +1,20 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. +# compiled output +dist/ +dist-tests/ +declarations/ -/dist/ -/tmp/ +# from scenarios +tmp/ +config/optional-features.json +ember-cli-build.cjs -# dependencies +# npm/pnpm/yarn pack output +*.tgz + +# deps & caches node_modules/ +.eslintcache +.prettiercache -# misc -/.eslintcache -/addon/.eslintcache -.env* -.pnpm-debug.log -npm-debug.log* -yarn-error.log +# potentially containing secrets +*.local diff --git a/packages/boxel-ui/addon/.prettierignore b/packages/boxel-ui/.prettierignore similarity index 56% rename from packages/boxel-ui/addon/.prettierignore rename to packages/boxel-ui/.prettierignore index b0381f9e2e..b5f539be61 100644 --- a/packages/boxel-ui/addon/.prettierignore +++ b/packages/boxel-ui/.prettierignore @@ -2,8 +2,15 @@ /blueprints/*/files/ # compiled output -/declarations/ /dist/ +/dist-*/ +/declarations/ # misc /coverage/ +pnpm-lock.yaml +config/ember-cli-update.json +*.yaml +*.yml +*.md +*.html diff --git a/packages/boxel-ui/.prettierrc.mjs b/packages/boxel-ui/.prettierrc.mjs new file mode 100644 index 0000000000..57b2d209ba --- /dev/null +++ b/packages/boxel-ui/.prettierrc.mjs @@ -0,0 +1,11 @@ +export default { + plugins: ['prettier-plugin-ember-template-tag'], + overrides: [ + { + files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', + options: { + singleQuote: true, + }, + }, + ], +}; diff --git a/packages/boxel-ui/addon/.template-lintrc.js b/packages/boxel-ui/.template-lintrc.mjs similarity index 79% rename from packages/boxel-ui/addon/.template-lintrc.js rename to packages/boxel-ui/.template-lintrc.mjs index cab4f56b28..ab0fa6423e 100644 --- a/packages/boxel-ui/addon/.template-lintrc.js +++ b/packages/boxel-ui/.template-lintrc.mjs @@ -1,8 +1,6 @@ -'use strict'; - -module.exports = { +export default { extends: ['recommended', '@cardstack/template-lint:recommended'], - plugins: ['../../template-lint/plugin'], + plugins: ['../template-lint/plugin'], rules: { 'no-pointer-down-event-binding': false, 'no-positive-tabindex': false, diff --git a/packages/boxel-ui/CONTRIBUTING.md b/packages/boxel-ui/CONTRIBUTING.md new file mode 100644 index 0000000000..96c649284f --- /dev/null +++ b/packages/boxel-ui/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How To Contribute + +## Installation + +- `git clone ` +- `cd @cardstack/boxel-ui` +- `pnpm install` + +## Linting + +- `pnpm lint` +- `pnpm lint:fix` + +## Building the addon + +- `pnpm build` + +## Running tests + +- `pnpm test` – Runs the test suite on the current Ember version +- `pnpm test:watch` – Runs the test suite in "watch mode" + +## Running the test application + +- `pnpm start` +- Visit the test application at [http://localhost:4200](http://localhost:4200). + +For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). diff --git a/packages/boxel-ui/LICENSE.md b/packages/boxel-ui/LICENSE.md new file mode 100644 index 0000000000..d354012c6c --- /dev/null +++ b/packages/boxel-ui/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/boxel-ui/README.md b/packages/boxel-ui/README.md index 499e83f903..83761b00b4 100644 --- a/packages/boxel-ui/README.md +++ b/packages/boxel-ui/README.md @@ -1,25 +1,26 @@ -## How to build this addon +# @cardstack/boxel-ui -### `pnpm build` in the addon/ dir +[Short description of the addon.] -This command runs the rollup build to create the consumable v2 addon format that is used by the test-app as well as the host package. +## Compatibility -### Or... `pnpm start` in the addon/ dir +- Ember.js v5.8 or above +- Embroider or ember-auto-import v2 -This command does the same thing as `pnpm build` but then watches for changes to the addon directory and re-runs the build when somethng changes. +## Installation -## Notes on rebuild scripts +``` +ember install @cardstack/boxel-ui +``` -These scripts do not run as part of the build steps above and should be run when changes you have made dictate. +## Usage -### `pnpm rebuild:icons` +[Longer description of how to use the addon in apps.] -Icon components in addon/src/icons are code-generated from svg files using `pnpm rebuild:icons` from the addon project. This script also generates src/icons.ts, which is the module that re-exports the icons for consumers of this addon. +## Contributing -This script should be run when an icon is added, removed, updated, or renamed. +See the [Contributing](CONTRIBUTING.md) guide for details. -### `pnpm rebuild:usage` +## License -This script generates src/usage.ts, which is the module that re-exports the usage modules that the test-app uses to include in it's component explorer UI. - -This script should be run when a usage file is added, removed, or renamed. +This project is licensed under the [MIT License](LICENSE.md). diff --git a/packages/boxel-ui/addon/addon-main.cjs b/packages/boxel-ui/addon-main.cjs similarity index 99% rename from packages/boxel-ui/addon/addon-main.cjs rename to packages/boxel-ui/addon-main.cjs index d36b0c86da..f868d6b91e 100644 --- a/packages/boxel-ui/addon/addon-main.cjs +++ b/packages/boxel-ui/addon-main.cjs @@ -1,5 +1,4 @@ 'use strict'; const { addonV1Shim } = require('@embroider/addon-shim'); - module.exports = addonV1Shim(__dirname); diff --git a/packages/boxel-ui/addon/.gitignore b/packages/boxel-ui/addon/.gitignore deleted file mode 100644 index 0f93238423..0000000000 --- a/packages/boxel-ui/addon/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# The authoritative copies of these live in the monorepo root (because they're -# more useful on github that way), but the build copies them into here so they -# will also appear in published NPM packages. -/LICENSE.md -/README.md - -# compiled output -/declarations -/dist - -# npm/pnpm/yarn pack output -*.tgz diff --git a/packages/boxel-ui/addon/.prettierrc.cjs b/packages/boxel-ui/addon/.prettierrc.cjs deleted file mode 100644 index d6a52956db..0000000000 --- a/packages/boxel-ui/addon/.prettierrc.cjs +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = { - plugins: ['prettier-plugin-ember-template-tag'], - singleQuote: true, -}; diff --git a/packages/boxel-ui/addon/babel.config.json b/packages/boxel-ui/addon/babel.config.json deleted file mode 100644 index 6c7c9c87b5..0000000000 --- a/packages/boxel-ui/addon/babel.config.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "plugins": [ - "ember-concurrency/async-arrow-task-transform", - [ - "@babel/plugin-transform-typescript", - { - "allExtensions": true, - "onlyRemoveTypeImports": true, - "allowDeclareFields": true - } - ], - "@embroider/addon-dev/template-colocation-plugin", - "@babel/plugin-transform-class-static-block", - [ - "babel-plugin-ember-template-compilation", - { - "targetFormat": "hbs", - "transforms": ["glimmer-scoped-css/ast-transform"] - } - ], - ["@babel/plugin-proposal-decorators", { "version": "legacy" }], - "@babel/plugin-transform-class-properties" - ] -} diff --git a/packages/boxel-ui/addon/bin/conditional-build.sh b/packages/boxel-ui/addon/bin/conditional-build.sh deleted file mode 100755 index d56f58bf64..0000000000 --- a/packages/boxel-ui/addon/bin/conditional-build.sh +++ /dev/null @@ -1,29 +0,0 @@ -#! /bin/sh - -CURRENT_DIR="$(pwd)" -SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)" - -SRC_DIR="${SCRIPTS_DIR}/../src" -DIST_DIR="${SCRIPTS_DIR}/../dist" - -if [ ! -d "$DIST_DIR" ]; then - echo "boxel-ui/addon dist dir does not exist. Building in ${SRC_DIR}/.." - cd "${SRC_DIR}/.." - pnpm run build - exit 0 -fi -if [ "$(uname)" = "Darwin" ]; then - mod_time_src=$(find "$SRC_DIR" -type f -exec stat -f %m {} + | sort -nr | head -n1) - mod_time_dist=$(find "$DIST_DIR" -type f -exec stat -f %m {} + | sort -nr | head -n1) -else - mod_time_src=$(find "$SRC_DIR" -type f -printf "%T@\n" | sort -nr | head -n1 | cut -d. -f1) - mod_time_dist=$(find "$DIST_DIR" -type f -printf "%T@\n" | sort -nr | head -n1 | cut -d. -f1) -fi - -if [ "$mod_time_src" -gt "$mod_time_dist" ]; then - echo "boxel-ui/addon dist dir is out of date. Building in ${SRC_DIR}/.." - cd "${SRC_DIR}/.." - pnpm run build -else - echo "✔️ boxel-ui/addon dist dir is up to date" -fi diff --git a/packages/boxel-ui/addon/rollup.config.mjs b/packages/boxel-ui/addon/rollup.config.mjs deleted file mode 100644 index 123f6fec0f..0000000000 --- a/packages/boxel-ui/addon/rollup.config.mjs +++ /dev/null @@ -1,91 +0,0 @@ -import { Addon } from '@embroider/addon-dev/rollup'; -import { babel } from '@rollup/plugin-babel'; -import { scopedCSS } from 'glimmer-scoped-css/rollup'; -import copy from 'rollup-plugin-copy'; - -const addon = new Addon({ - srcDir: 'src', - destDir: 'dist', -}); - -export default { - // This provides defaults that work well alongside `publicEntrypoints` below. - // You can augment this if you need to. - output: addon.output(), - - plugins: [ - scopedCSS('src'), - - // These are the modules that users should be able to import from your - // addon. Anything not listed here may get optimized away. - addon.publicEntrypoints([ - 'components.js', - 'helpers.js', - 'icons.js', - 'modifiers.js', - 'usage.js', - ]), - - // These are the modules that should get reexported into the traditional - // "app" tree. Things in here should also be in publicEntrypoints above, but - // not everything in publicEntrypoints necessarily needs to go here. - addon.appReexports([]), - - // Follow the V2 Addon rules about dependencies. Your code can import from - // `dependencies` and `peerDependencies` as well as standard Ember-provided - // package names. - addon.dependencies(), - - // Ensure that standalone .hbs files are properly integrated as Javascript. - addon.hbs(), - - // Ensure that .gjs files are properly integrated as Javascript - addon.gjs(), - - // css is importable for side-effect - addon.keepAssets(['**/*.css']), - - // these asset types are imported for their URLs - addon.keepAssets( - ['**/*.otf', '**/*.png', '**/*.webp', '**/*.woff2'], - 'default', - ), - - // Remove leftover build artifacts when starting a new build. - addon.clean({ runOnce: true }), - - // Copy Readme and License into published package - copy({ - targets: [ - { src: '../README.md', dest: '.' }, - { src: '../LICENSE.md', dest: '.' }, - { src: './src/styles/*.{css,woff2,otf}', dest: './dist/styles' }, - { src: './src/styles/LICENSE.txt', dest: './dist/styles' }, - ], - // this makes it late enough that the `clean()` hook above doesn't remove - // our copied files - hook: 'generateBundle', - }), - - // This babel config should *not* apply presets or compile away ES modules. - // It exists only to provide development niceties for you, like automatic - // template colocation. - // - // By default, this will load the actual babel config from the file - // babel.config.json. - babel({ - babelHelpers: 'bundled', - extensions: ['.js', '.gjs', '.ts', '.gts'], - }), - ], - - onLog(level, log, handler) { - if (log.code === 'UNUSED_EXTERNAL_IMPORT') { - // Turn unused external imports from warnings to errors. Warnings - // accumulate and just generate noise. And this is an easily-fixable issue - // usually caused by failing to declare a type import correctly. - level = 'error'; - } - handler(level, log); - }, -}; diff --git a/packages/boxel-ui/addon/src/styles/fonts.css b/packages/boxel-ui/addon/src/styles/fonts.css deleted file mode 100644 index e8f8c52375..0000000000 --- a/packages/boxel-ui/addon/src/styles/fonts.css +++ /dev/null @@ -1,178 +0,0 @@ -@font-face { - font-family: 'IBM Plex Sans'; - font-style: normal; - font-weight: 100 700; - font-display: swap; - src: - local('IBM Plex Sans Var'), - local('IBMPlexSansVar'), - local('IBM Plex Sans Var Regular'), - local('IBMPlexSansVar-Regular'), - url('./IBMPlexSansVar-Roman.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Sans'; - font-style: italic; - font-weight: 100 700; - font-display: swap; - src: - local('IBM Plex Sans Var Italic'), - local('IBMPlexSansVar-Italic'), - url('./IBMPlexSansVar-Italic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 400; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Mono'), - local('IBMPlexMono'), - url('./IBMPlexMono-Regular.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 500; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Mono Medium'), - url('./IBMPlexMono-Medium.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 600; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Mono SemiBold'), - url('./IBMPlexMono-SemiBold.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 700; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Mono Bold'), - local('IBMPlexMono-Bold'), - url('./IBMPlexMono-Bold.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 400; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Mono Italic'), - url('./IBMPlexMono-Italic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 500; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Mono Medium Italic'), - url('./IBMPlexMono-MediumItalic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 600; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Mono SemiBold Italic'), - url('./IBMPlexMono-SemiBoldItalic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-weight: 700; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Mono Bold Italic'), - url('./IBMPlexMono-BoldItalic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 400; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Serif'), - local('IBM Plex Serif Regular'), - local('IBMPlexSerif'), - local('IBMPlexSerif-Regular'), - url('./IBMPlexSerif-Regular.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 500; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Serif Medium'), - local('IBMPlexSerif-Medium'), - url('./IBMPlexSerif-Medium.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 600; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Serif SemiBold'), - local('IBMPlexSerif-SemiBold'), - url('./IBMPlexSerif-SemiBold.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 700; - font-style: normal; - font-display: swap; - src: - local('IBM Plex Serif Bold'), - local('IBMPlexSerif-Bold'), - url('./IBMPlexSerif-Bold.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 400; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Serif Italic'), - local('IBMPlexSerif-Italic'), - url('./IBMPlexSerif-Italic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 500; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Serif Medium Italic'), - local('IBMPlexSerif-MediumItalic'), - url('./IBMPlexSerif-MediumItalic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 600; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Serif SemiBold Italic'), - local('IBMPlexSerif-SemiBoldItalic'), - url('./IBMPlexSerif-SemiBoldItalic.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Serif'; - font-weight: 700; - font-style: italic; - font-display: swap; - src: - local('IBM Plex Serif Bold Italic'), - local('IBMPlexSerif-BoldItalic'), - url('./IBMPlexSerif-BoldItalic.woff2') format('woff2'); -} diff --git a/packages/boxel-ui/addon/tsconfig.json b/packages/boxel-ui/addon/tsconfig.json deleted file mode 100644 index d20d11dcf6..0000000000 --- a/packages/boxel-ui/addon/tsconfig.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "extends": "@tsconfig/ember/tsconfig.json", - "compilerOptions": { - "allowImportingTsExtensions": true, - "allowJs": true, - "allowSyntheticDefaultImports": true, - "alwaysStrict": true, - "declarationDir": "declarations", - "esModuleInterop": true, - "experimentalDecorators": true, - "inlineSourceMap": true, - "inlineSources": true, - "module": "nodenext", - "moduleResolution": "nodenext", - "noEmit": true, - "noEmitOnError": false, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, - "target": "es2020", - "traceResolution": false, - "paths": { - "https://cardstack.com/base/*": [ - "../../base/*" - ], - "*": [ - "./src/types/*" - ], - "@cardstack/boxel-ui": [ - "./src" - ], - "@cardstack/boxel-ui/*": [ - "./src/*" - ] - }, - "types": [ - "@cardstack/local-types" - ] - }, - "include": [ - "src/**/*", - "unpublished-development-types/**/*" - ] -} diff --git a/packages/boxel-ui/babel.config.cjs b/packages/boxel-ui/babel.config.cjs new file mode 100644 index 0000000000..be1c94b875 --- /dev/null +++ b/packages/boxel-ui/babel.config.cjs @@ -0,0 +1,46 @@ +/** + * This babel.config is not used for publishing. + * It's only for the local editing experience + * (and linting) + */ + +const { + babelCompatSupport, + templateCompatSupport, +} = require('@embroider/compat/babel'); + +module.exports = { + plugins: [ + 'ember-concurrency/async-arrow-task-transform', + [ + '@babel/plugin-transform-typescript', + { + allExtensions: true, + allowDeclareFields: true, + onlyRemoveTypeImports: true, + }, + ], + [ + 'babel-plugin-ember-template-compilation', + { + transforms: [ + ...templateCompatSupport(), + 'glimmer-scoped-css/ast-transform', + ], + }, + ], + [ + 'module:decorator-transforms', + { + runtime: { + import: require.resolve('decorator-transforms/runtime-esm'), + }, + }, + ], + ...babelCompatSupport(), + ], + + generatorOpts: { + compact: false, + }, +}; diff --git a/packages/boxel-ui/addon/bin/rebuild-icons.mjs b/packages/boxel-ui/bin/rebuild-icons.mjs similarity index 100% rename from packages/boxel-ui/addon/bin/rebuild-icons.mjs rename to packages/boxel-ui/bin/rebuild-icons.mjs diff --git a/packages/boxel-ui/addon/bin/rebuild-usage.mjs b/packages/boxel-ui/bin/rebuild-usage.mjs similarity index 100% rename from packages/boxel-ui/addon/bin/rebuild-usage.mjs rename to packages/boxel-ui/bin/rebuild-usage.mjs diff --git a/packages/boxel-ui/config/ember-cli-update.json b/packages/boxel-ui/config/ember-cli-update.json index ffb2aa2477..15ced9d098 100644 --- a/packages/boxel-ui/config/ember-cli-update.json +++ b/packages/boxel-ui/config/ember-cli-update.json @@ -1,16 +1,19 @@ { "schemaVersion": "1.0.0", + "projectName": "@cardstack/boxel-ui", "packages": [ { - "name": "ember-cli", - "version": "4.12.1", + "name": "@ember/addon-blueprint", + "version": "0.17.1", "blueprints": [ { - "name": "addon", - "outputRepo": "https://github.com/ember-cli/ember-addon-output", - "codemodsSource": "ember-addon-codemods-manifest@1", + "name": "@ember/addon-blueprint", "isBaseBlueprint": true, - "options": ["--no-welcome"] + "options": [ + "--ci-provider=github", + "--pnpm", + "--typescript" + ] } ] } diff --git a/packages/boxel-ui/config/environment.js b/packages/boxel-ui/config/environment.js deleted file mode 100644 index da5cbbff84..0000000000 --- a/packages/boxel-ui/config/environment.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -module.exports = function (environment) { - let ENV = { - modulePrefix: 'dummy', - environment, - rootURL: '/', - locationType: 'history', - EmberENV: { - FEATURES: { - // Here you can enable experimental features on an ember canary build - EMBER_NATIVE_DECORATOR_SUPPORT: true, - EMBER_METAL_TRACKED_PROPERTIES: true, - EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP: true, - EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS: true, - EMBER_GLIMMER_FN_HELPER: true, - EMBER_GLIMMER_ON_MODIFIER: true, - }, - EXTEND_PROTOTYPES: { - // Prevent Ember Data from overriding Date.parse. - Date: false, - }, - }, - - APP: { - // Here you can pass flags/options to your application instance - // when it is created - }, - }; - - if (environment === 'development') { - // ENV.APP.LOG_RESOLVER = true; - // ENV.APP.LOG_ACTIVE_GENERATION = true; - // ENV.APP.LOG_TRANSITIONS = true; - // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; - // ENV.APP.LOG_VIEW_LOOKUPS = true; - } - - if (environment === 'test') { - // Testem prefers this... - ENV.locationType = 'none'; - - // keep test console output quieter - ENV.APP.LOG_ACTIVE_GENERATION = false; - ENV.APP.LOG_VIEW_LOOKUPS = false; - - ENV.APP.rootElement = '#ember-testing'; - ENV.APP.autoboot = false; - } - - if (environment === 'production') { - // here you can enable a production-specific feature - ENV.rootURL = '/boxel-ui/'; - ENV.locationType = 'hash'; - } - - return ENV; -}; diff --git a/packages/boxel-ui/config/optional-features.json b/packages/boxel-ui/config/optional-features.json deleted file mode 100644 index 9964054535..0000000000 --- a/packages/boxel-ui/config/optional-features.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "application-template-wrapper": false, - "template-only-glimmer-components": true -} diff --git a/packages/boxel-ui/demo-app/app.gts b/packages/boxel-ui/demo-app/app.gts new file mode 100644 index 0000000000..294402396b --- /dev/null +++ b/packages/boxel-ui/demo-app/app.gts @@ -0,0 +1,40 @@ +import '../src/styles/global.css'; +import '../src/styles/fonts.css'; +import '../src/styles/variables.css'; + +import EmberRouter from '@ember/routing/router'; +import EmberFreestyleService from 'ember-freestyle/services/ember-freestyle'; +import PageTitleService from 'ember-page-title/services/page-title'; +import PowerCalendarService from 'ember-power-calendar/services/power-calendar'; +import EmberApp from 'ember-strict-application-resolver'; + +class Router extends EmberRouter { + location = 'history'; + rootURL = '/'; +} + +export class App extends EmberApp { + /** + * Any services or anything from the addon that needs to be in the app-tree registry + * will need to be manually specified here. + * + * Techniques to avoid needing this: + * - private services + * - require the consuming app import and configure themselves + * (which is what we're emulating here) + */ + modules = { + './router': Router, + './services/page-title': PageTitleService, + './services/ember-freestyle': EmberFreestyleService, + './services/power-calendar': PowerCalendarService, + ...import.meta.glob( + ['./controllers/**/*', './routes/**/*', './templates/**/*'], + { + eager: true, + }, + ), + }; +} + +Router.map(function () {}); diff --git a/packages/boxel-ui/test-app/app/components/.gitkeep b/packages/boxel-ui/demo-app/components/.gitkeep similarity index 100% rename from packages/boxel-ui/test-app/app/components/.gitkeep rename to packages/boxel-ui/demo-app/components/.gitkeep diff --git a/packages/boxel-ui/test-app/app/components/icons-grid.gts b/packages/boxel-ui/demo-app/components/icons-grid.gts similarity index 100% rename from packages/boxel-ui/test-app/app/components/icons-grid.gts rename to packages/boxel-ui/demo-app/components/icons-grid.gts index 1371244c64..d5cd5396b2 100644 --- a/packages/boxel-ui/test-app/app/components/icons-grid.gts +++ b/packages/boxel-ui/demo-app/components/icons-grid.gts @@ -1,8 +1,8 @@ -import Component from '@glimmer/component'; import { ALL_ICON_COMPONENTS } from '@cardstack/boxel-icons/boxel-icons-meta'; import { Tooltip } from '@cardstack/boxel-ui/components'; -import { tracked } from '@glimmer/tracking'; import { on } from '@ember/modifier'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; function importStatement(name: string): string { return `import ${toPascalCase(name)} from '@cardstack/boxel-icons/${name}';`; diff --git a/packages/boxel-ui/test-app/app/controllers/index.ts b/packages/boxel-ui/demo-app/controllers/index.ts similarity index 100% rename from packages/boxel-ui/test-app/app/controllers/index.ts rename to packages/boxel-ui/demo-app/controllers/index.ts diff --git a/packages/boxel-ui/test-app/app/helpers/format-component-name.ts b/packages/boxel-ui/demo-app/helpers/format-component-name.ts similarity index 99% rename from packages/boxel-ui/test-app/app/helpers/format-component-name.ts rename to packages/boxel-ui/demo-app/helpers/format-component-name.ts index 758e4f603d..e4bd9db77d 100644 --- a/packages/boxel-ui/test-app/app/helpers/format-component-name.ts +++ b/packages/boxel-ui/demo-app/helpers/format-component-name.ts @@ -1,5 +1,6 @@ import { helper } from '@ember/component/helper'; import { typeOf } from '@ember/utils'; + import { titleize } from '../utils/titleize.ts'; export default helper(function formatComponentName([componentPath]: [string]) { diff --git a/packages/boxel-ui/test-app/app/routes/index.ts b/packages/boxel-ui/demo-app/routes/index.ts similarity index 100% rename from packages/boxel-ui/test-app/app/routes/index.ts rename to packages/boxel-ui/demo-app/routes/index.ts diff --git a/packages/boxel-ui/demo-app/templates/application.gts b/packages/boxel-ui/demo-app/templates/application.gts new file mode 100644 index 0000000000..e8aebeb772 --- /dev/null +++ b/packages/boxel-ui/demo-app/templates/application.gts @@ -0,0 +1,8 @@ +import 'ember-basic-dropdown/styles'; + +import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown-wormhole'; + + diff --git a/packages/boxel-ui/test-app/app/templates/index.gts b/packages/boxel-ui/demo-app/templates/index.gts similarity index 97% rename from packages/boxel-ui/test-app/app/templates/index.gts rename to packages/boxel-ui/demo-app/templates/index.gts index 657806c98d..a9b2acbf3b 100644 --- a/packages/boxel-ui/test-app/app/templates/index.gts +++ b/packages/boxel-ui/demo-app/templates/index.gts @@ -1,34 +1,29 @@ +import { + BoxelContainer, + BoxelSelect, + FieldContainer, + Switch, +} from '@cardstack/boxel-ui/components'; +import { ALL_USAGE_COMPONENTS } from '@cardstack/boxel-ui/usage'; import { action } from '@ember/object'; import type Owner from '@ember/owner'; -import Component from '@glimmer/component'; -import type { ComponentLike } from '@glint/template'; -import { tracked } from '@glimmer/tracking'; import type RouterService from '@ember/routing/router-service'; import { service } from '@ember/service'; - +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import type { ComponentLike } from '@glint/template'; import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown'; - import FreestyleGuide from 'ember-freestyle/components/freestyle-guide'; import FreestyleSection from 'ember-freestyle/components/freestyle-section'; import { pageTitle } from 'ember-page-title'; -import RouteTemplate from 'ember-route-template'; -import { ALL_USAGE_COMPONENTS } from '@cardstack/boxel-ui/usage'; -import Themes, { type Theme } from '../themes/index'; import IconsGrid from '../components/icons-grid'; - -import { - BoxelSelect, - FieldContainer, - Switch, - BoxelContainer, -} from '@cardstack/boxel-ui/components'; - import formatComponentName from '../helpers/format-component-name'; +import Themes, { type Theme } from '../themes/index'; interface UsageComponent { - title: string; component: ComponentLike; + title: string; } class IndexComponent extends Component { @@ -105,7 +100,7 @@ class IndexComponent extends Component { - private intervalId?: NodeJS.Timeout; + private intervalId?: ReturnType; private themes: Theme[] = [{ name: '' }, ...Themes]; private usageComponents = ALL_USAGE_COMPONENTS.map(([name, c]) => { return { @@ -173,4 +168,4 @@ class IndexComponent extends Component { }; } -export default RouteTemplate(IndexComponent); +export default IndexComponent; diff --git a/packages/boxel-ui/test-app/app/themes/boxel.ts b/packages/boxel-ui/demo-app/themes/boxel.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/boxel.ts rename to packages/boxel-ui/demo-app/themes/boxel.ts diff --git a/packages/boxel-ui/test-app/app/themes/bubblegum.ts b/packages/boxel-ui/demo-app/themes/bubblegum.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/bubblegum.ts rename to packages/boxel-ui/demo-app/themes/bubblegum.ts diff --git a/packages/boxel-ui/test-app/app/themes/candyland.ts b/packages/boxel-ui/demo-app/themes/candyland.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/candyland.ts rename to packages/boxel-ui/demo-app/themes/candyland.ts diff --git a/packages/boxel-ui/test-app/app/themes/dark-studio.ts b/packages/boxel-ui/demo-app/themes/dark-studio.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/dark-studio.ts rename to packages/boxel-ui/demo-app/themes/dark-studio.ts diff --git a/packages/boxel-ui/test-app/app/themes/doom64.ts b/packages/boxel-ui/demo-app/themes/doom64.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/doom64.ts rename to packages/boxel-ui/demo-app/themes/doom64.ts diff --git a/packages/boxel-ui/test-app/app/themes/index.ts b/packages/boxel-ui/demo-app/themes/index.ts similarity index 94% rename from packages/boxel-ui/test-app/app/themes/index.ts rename to packages/boxel-ui/demo-app/themes/index.ts index 5946aa0a47..a5846e074a 100644 --- a/packages/boxel-ui/test-app/app/themes/index.ts +++ b/packages/boxel-ui/demo-app/themes/index.ts @@ -2,16 +2,16 @@ import { extractCssVariables, sanitizeHtmlSafe, } from '@cardstack/boxel-ui/helpers'; -import { htmlSafe, type SafeString } from '@ember/template'; +import { type SafeString, htmlSafe } from '@ember/template'; +import { Boxel } from './boxel.ts'; import { Bubblegum } from './bubblegum.ts'; -import { NeoBrutalism } from './neo-brutalism.ts'; -import { SoftPop } from './soft-pop.ts'; import { Candyland } from './candyland.ts'; +import { DarkStudio } from './dark-studio.ts'; import { Doom64 } from './doom64.ts'; +import { NeoBrutalism } from './neo-brutalism.ts'; +import { SoftPop } from './soft-pop.ts'; import { StarryNight } from './starry-night.ts'; -import { Boxel } from './boxel.ts'; -import { DarkStudio } from './dark-studio.ts'; export interface Theme { name: string; diff --git a/packages/boxel-ui/test-app/app/themes/neo-brutalism.ts b/packages/boxel-ui/demo-app/themes/neo-brutalism.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/neo-brutalism.ts rename to packages/boxel-ui/demo-app/themes/neo-brutalism.ts diff --git a/packages/boxel-ui/test-app/app/themes/soft-pop.ts b/packages/boxel-ui/demo-app/themes/soft-pop.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/soft-pop.ts rename to packages/boxel-ui/demo-app/themes/soft-pop.ts diff --git a/packages/boxel-ui/test-app/app/themes/starry-night.ts b/packages/boxel-ui/demo-app/themes/starry-night.ts similarity index 100% rename from packages/boxel-ui/test-app/app/themes/starry-night.ts rename to packages/boxel-ui/demo-app/themes/starry-night.ts diff --git a/packages/boxel-ui/test-app/app/utils/titleize.ts b/packages/boxel-ui/demo-app/utils/titleize.ts similarity index 100% rename from packages/boxel-ui/test-app/app/utils/titleize.ts rename to packages/boxel-ui/demo-app/utils/titleize.ts index 3a09c7b482..80a5d4a389 100644 --- a/packages/boxel-ui/test-app/app/utils/titleize.ts +++ b/packages/boxel-ui/demo-app/utils/titleize.ts @@ -1,5 +1,5 @@ -import { typeOf } from '@ember/utils'; import { capitalize } from '@ember/string'; +import { typeOf } from '@ember/utils'; export function titleize(val: string): string | undefined { if (!val || typeOf(val) !== 'string') { diff --git a/packages/boxel-ui/addon/bin/generate-component-specs.mjs b/packages/boxel-ui/generate-component-specs.mjs similarity index 97% rename from packages/boxel-ui/addon/bin/generate-component-specs.mjs rename to packages/boxel-ui/generate-component-specs.mjs index eec097b344..eec7d6e3cc 100644 --- a/packages/boxel-ui/addon/bin/generate-component-specs.mjs +++ b/packages/boxel-ui/generate-component-specs.mjs @@ -21,7 +21,13 @@ const ADDON_DIR = path.resolve(SCRIPT_DIR, '..'); const REPO_ROOT = path.resolve(ADDON_DIR, '..', '..', '..'); const COMPONENTS_DIR = path.join(ADDON_DIR, 'src', 'components'); -const CATALOG_DIR = path.join(REPO_ROOT, 'packages', 'catalog', 'contents', 'Spec'); +const CATALOG_DIR = path.join( + REPO_ROOT, + 'packages', + 'catalog', + 'contents', + 'Spec', +); const BARREL_FILE = path.join(ADDON_DIR, 'src', 'components.ts'); const SPEC_MODULE = '@cardstack/boxel-ui/components'; @@ -37,7 +43,8 @@ const SPEC_FILE_PREFIX = 'boxel-ui-'; // that doesn't actually exist. function buildBarrelExportMap() { const source = fs.readFileSync(BARREL_FILE, 'utf8'); - const re = /^import\s+([A-Za-z0-9_$]+)[^;]*\s+from\s+['"]\.\/components\/([a-z0-9-]+)\/index\.gts['"]/gm; + const re = + /^import\s+([A-Za-z0-9_$]+)[^;]*\s+from\s+['"]\.\/components\/([a-z0-9-]+)\/index\.gts['"]/gm; const candidates = new Map(); let m; while ((m = re.exec(source)) !== null) { @@ -104,7 +111,9 @@ function extractPrimaryUsageBlock(source) { function extractStringAttr(text, name) { // @name='value' or @name="value" - const re = new RegExp(`@${name}=(?:'((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)")`); + const re = new RegExp( + `@${name}=(?:'((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)")`, + ); const m = text.match(re); if (!m) return null; return (m[1] ?? m[2]).replace(/\\'/g, "'").replace(/\\"/g, '"'); @@ -152,10 +161,7 @@ function extractOptions(text, source) { // const (`const validBottomTreatments = [...]`) shapes when they're array // literals — which is most of the enum cases in usage.gts files. if (source) { - const re = new RegExp( - `(?:^|\\b)${ref}\\s*=\\s*\\[([^\\]]*)\\]`, - 'm', - ); + const re = new RegExp(`(?:^|\\b)${ref}\\s*=\\s*\\[([^\\]]*)\\]`, 'm'); const m = source.match(re); if (m) { const opts = []; @@ -399,9 +405,7 @@ function buildReadme({ } sections.push('## Import'); sections.push( - '```ts\n' + - `import { ${componentName} } from '${SPEC_MODULE}';\n` + - '```', + '```ts\n' + `import { ${componentName} } from '${SPEC_MODULE}';\n` + '```', ); sections.push('## API'); sections.push(buildApiTable(args)); diff --git a/packages/boxel-ui/index.html b/packages/boxel-ui/index.html new file mode 100644 index 0000000000..612b852a4b --- /dev/null +++ b/packages/boxel-ui/index.html @@ -0,0 +1,20 @@ + + + + + + Demo App + + + + + + + + + + diff --git a/packages/boxel-ui/addon/package.json b/packages/boxel-ui/package.json similarity index 52% rename from packages/boxel-ui/addon/package.json rename to packages/boxel-ui/package.json index c76ca73e35..7344d7158b 100644 --- a/packages/boxel-ui/addon/package.json +++ b/packages/boxel-ui/package.json @@ -8,30 +8,36 @@ "repository": "", "license": "MIT", "author": "", - "directories": { - "doc": "doc", - "test": "tests" + "imports": { + "#src/*": "./src/*", + "#tests/helpers": "./tests/helpers/index.ts" }, + "exports": { + ".": "./src/index.ts", + "./addon-main.js": "./addon-main.cjs", + "./*": "./src/*.ts" + }, + "files": [ + "addon-main.cjs", + "declarations", + "dist", + "src" + ], "scripts": { - "build": "concurrently \"pnpm:build:*\" --names \"build:\"", - "build:js": "rollup --config", - "build:types": "ember-tsc --declaration --emitDeclarationOnly --noEmit false", - "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\"", - "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\"", - "lint:hbs": "ember-template-lint .", - "lint:hbs:fix": "ember-template-lint . --fix", + "format": "prettier . --cache --write", + "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", + "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format", + "lint:format": "prettier . --cache --check", + "lint:hbs": "ember-template-lint . --no-error-on-unmatched-pattern", + "lint:hbs:fix": "ember-template-lint . --fix --no-error-on-unmatched-pattern", "lint:js": "eslint . --report-unused-disable-directives --cache", "lint:js:fix": "eslint . --report-unused-disable-directives --fix", "lint:types": "ember-tsc --noEmit", "rebuild:icons": "node bin/rebuild-icons.mjs", "rebuild:usage": "node bin/rebuild-usage.mjs", "generate:component-specs": "node bin/generate-component-specs.mjs", - "prepack": "rollup --config", - "start": "concurrently \"pnpm:start:*\" --names \"start:\"", - "start:js": "rollup --config --watch --no-watch.clearScreen", - "start:types": "ember-tsc --declaration --emitDeclarationOnly --watch", - "test": "echo 'A v2 addon does not have tests, run tests in test-app'", - "test:ember": "ember test" + "start": "vite dev", + "test": "vite build --mode=development --out-dir dist-tests && testem --file testem.cjs ci --port 0" }, "dependencies": { "@cardstack/boxel-icons": "workspace:*", @@ -46,7 +52,7 @@ "dayjs": "catalog:", "dompurify": "catalog:", "ember-animated": "catalog:", - "ember-basic-dropdown": "8.0.4", + "ember-basic-dropdown": "^9.0.0", "ember-css-url": "^1.0.0", "ember-concurrency": "catalog:", "ember-concurrency-ts": "catalog:", @@ -56,7 +62,7 @@ "ember-modifier": "^4.1.0", "ember-power-calendar": "^1.2.0", "ember-power-calendar-moment": "^1.0.2", - "ember-power-select": "^8.0.0", + "ember-power-select": "^9.0.0", "ember-resize-modifier": "^0.7.1", "ember-set-body-class": "^1.0.2", "ember-sortable": "^5.3.1", @@ -65,32 +71,44 @@ "focus-trap": "catalog:", "lodash": "catalog:", "pluralize": "catalog:", - "tracked-built-ins": "catalog:", - "typescript": "catalog:" + "tracked-built-ins": "catalog:" }, "devDependencies": { - "@babel/core": "catalog:", - "@babel/plugin-proposal-decorators": "catalog:", - "@babel/plugin-transform-class-properties": "catalog:", - "@babel/plugin-transform-class-static-block": "catalog:", - "@babel/plugin-transform-typescript": "catalog:", - "@babel/runtime": "catalog:", + "@babel/core": "^7.25.2", + "@babel/eslint-parser": "^7.25.1", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/runtime": "^7.25.6", "@cardstack/eslint-plugin-boxel": "workspace:*", - "@cardstack/local-types": "workspace:*", - "@embroider/addon-dev": "^8.0.0", - "@embroider/macros": "~1.16.5", - "@rollup/plugin-babel": "catalog:", - "@tsconfig/ember": "3.0.1", - "@types/dompurify": "catalog:", + "@ember/app-tsconfig": "^1.0.0", + "@ember/library-tsconfig": "^1.0.0", + "@ember/optional-features": "^2.2.0", + "@ember/test-helpers": "^5.2.1", + "@embroider/compat": "^4.1.0", + "@embroider/core": "^4.1.0", + "@embroider/macros": "^1.20.1", + "@embroider/vite": "^1.1.5", + "@eslint/js": "^9.17.0", + "@glimmer/component": "^2.0.0", + "@glint/ember-tsc": "^1.0.3", + "@glint/template": "^1.6.1", + "@glint/tsserver-plugin": "^2.0.0", + "@rollup/plugin-babel": "^6.0.4", "@types/lodash": "catalog:", "@types/pluralize": "catalog:", + "@types/qunit": "^2.19.12", "@typescript-eslint/eslint-plugin": "catalog:", "@typescript-eslint/parser": "catalog:", - "babel-plugin-ember-template-compilation": "catalog:", - "concurrently": "catalog:", - "ember-template-imports": "^4.1.1", - "ember-template-lint": "catalog:", - "ember-template-lint-plugin-prettier": "^5.0.0", + "axe-core": "^4.0.0", + "babel-plugin-ember-template-compilation": "^4.0.0", + "concurrently": "^9.0.1", + "ember-a11y-testing": "^8.0.0", + "ember-cli": "^5.12.0", + "ember-auto-import": "^2.10.0", + "ember-page-title": "^9.0.3", + "ember-qunit": "^9.0.2", + "ember-source": "^6.7.0", + "ember-strict-application-resolver": "^0.1.0", + "ember-template-lint": "^7.9.0", "eslint": "catalog:", "eslint-config-prettier": "catalog:", "eslint-plugin-ember": "catalog:", @@ -98,50 +116,24 @@ "eslint-plugin-prettier": "catalog:", "eslint-plugin-simple-import-sort": "catalog:", "eslint-plugin-typescript-sort-keys": "catalog:", + "decorator-transforms": "^2.2.2", "glimmer-scoped-css": "catalog:", - "prettier": "catalog:", - "prettier-plugin-ember-template-tag": "catalog:", - "rollup": "catalog:", - "rollup-plugin-copy": "catalog:", + "globals": "^16.1.0", + "prettier": "^3.4.2", + "prettier-plugin-ember-template-tag": "^2.0.4", + "qunit": "^2.24.1", + "qunit-dom": "^3.4.0", "svgo": "catalog:", - "@glint/ember-tsc": "catalog:" - }, - "peerDependencies": { - "ember-source": "catalog:", - "typescript": "catalog:" - }, - "engines": { - "node": "24.13.1" + "testem": "^3.15.1", + "typescript": "catalog:", + "vite": "^8.0.14" }, "ember": { "edition": "octane" }, "ember-addon": { - "app-js": {}, - "main": "addon-main.cjs", - "public-assets": {}, + "version": 2, "type": "addon", - "version": 2 - }, - "exports": { - "./*": { - "types": "./declarations/*.d.ts", - "default": "./dist/*.js" - }, - "./styles/*.css": "./dist/styles/*.css", - "./addon-main.js": "./addon-main.cjs" - }, - "files": [ - "addon-main.cjs", - "declarations", - "dist", - "public" - ], - "typesVersions": { - "*": { - "*": [ - "declarations/*" - ] - } + "main": "addon-main.cjs" } } diff --git a/packages/boxel-ui/addon/raw-icons/ai-bw.svg b/packages/boxel-ui/raw-icons/ai-bw.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/ai-bw.svg rename to packages/boxel-ui/raw-icons/ai-bw.svg diff --git a/packages/boxel-ui/addon/raw-icons/arrow-left.svg b/packages/boxel-ui/raw-icons/arrow-left.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/arrow-left.svg rename to packages/boxel-ui/raw-icons/arrow-left.svg diff --git a/packages/boxel-ui/addon/raw-icons/arrow-right.svg b/packages/boxel-ui/raw-icons/arrow-right.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/arrow-right.svg rename to packages/boxel-ui/raw-icons/arrow-right.svg diff --git a/packages/boxel-ui/addon/raw-icons/arrow-top-left.svg b/packages/boxel-ui/raw-icons/arrow-top-left.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/arrow-top-left.svg rename to packages/boxel-ui/raw-icons/arrow-top-left.svg diff --git a/packages/boxel-ui/addon/raw-icons/arrow-up.svg b/packages/boxel-ui/raw-icons/arrow-up.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/arrow-up.svg rename to packages/boxel-ui/raw-icons/arrow-up.svg diff --git a/packages/boxel-ui/addon/raw-icons/atom.svg b/packages/boxel-ui/raw-icons/atom.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/atom.svg rename to packages/boxel-ui/raw-icons/atom.svg diff --git a/packages/boxel-ui/addon/raw-icons/boxel-icon-with-text.svg b/packages/boxel-ui/raw-icons/boxel-icon-with-text.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/boxel-icon-with-text.svg rename to packages/boxel-ui/raw-icons/boxel-icon-with-text.svg diff --git a/packages/boxel-ui/addon/raw-icons/boxel-icon.svg b/packages/boxel-ui/raw-icons/boxel-icon.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/boxel-icon.svg rename to packages/boxel-ui/raw-icons/boxel-icon.svg diff --git a/packages/boxel-ui/addon/raw-icons/card-definition.svg b/packages/boxel-ui/raw-icons/card-definition.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/card-definition.svg rename to packages/boxel-ui/raw-icons/card-definition.svg diff --git a/packages/boxel-ui/addon/raw-icons/card-instance.svg b/packages/boxel-ui/raw-icons/card-instance.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/card-instance.svg rename to packages/boxel-ui/raw-icons/card-instance.svg diff --git a/packages/boxel-ui/addon/raw-icons/card.svg b/packages/boxel-ui/raw-icons/card.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/card.svg rename to packages/boxel-ui/raw-icons/card.svg diff --git a/packages/boxel-ui/addon/raw-icons/cardbot-lg.svg b/packages/boxel-ui/raw-icons/cardbot-lg.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/cardbot-lg.svg rename to packages/boxel-ui/raw-icons/cardbot-lg.svg diff --git a/packages/boxel-ui/addon/raw-icons/caret-down.svg b/packages/boxel-ui/raw-icons/caret-down.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/caret-down.svg rename to packages/boxel-ui/raw-icons/caret-down.svg diff --git a/packages/boxel-ui/addon/raw-icons/check-mark.svg b/packages/boxel-ui/raw-icons/check-mark.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/check-mark.svg rename to packages/boxel-ui/raw-icons/check-mark.svg diff --git a/packages/boxel-ui/addon/raw-icons/chevron-right.svg b/packages/boxel-ui/raw-icons/chevron-right.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/chevron-right.svg rename to packages/boxel-ui/raw-icons/chevron-right.svg diff --git a/packages/boxel-ui/addon/raw-icons/copy.svg b/packages/boxel-ui/raw-icons/copy.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/copy.svg rename to packages/boxel-ui/raw-icons/copy.svg diff --git a/packages/boxel-ui/addon/raw-icons/diagonal-arrow-left-up.svg b/packages/boxel-ui/raw-icons/diagonal-arrow-left-up.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/diagonal-arrow-left-up.svg rename to packages/boxel-ui/raw-icons/diagonal-arrow-left-up.svg diff --git a/packages/boxel-ui/addon/raw-icons/download.svg b/packages/boxel-ui/raw-icons/download.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/download.svg rename to packages/boxel-ui/raw-icons/download.svg diff --git a/packages/boxel-ui/addon/raw-icons/dropdown-arrow-down.svg b/packages/boxel-ui/raw-icons/dropdown-arrow-down.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/dropdown-arrow-down.svg rename to packages/boxel-ui/raw-icons/dropdown-arrow-down.svg diff --git a/packages/boxel-ui/addon/raw-icons/dropdown-arrow-filled.svg b/packages/boxel-ui/raw-icons/dropdown-arrow-filled.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/dropdown-arrow-filled.svg rename to packages/boxel-ui/raw-icons/dropdown-arrow-filled.svg diff --git a/packages/boxel-ui/addon/raw-icons/dropdown-arrow-up.svg b/packages/boxel-ui/raw-icons/dropdown-arrow-up.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/dropdown-arrow-up.svg rename to packages/boxel-ui/raw-icons/dropdown-arrow-up.svg diff --git a/packages/boxel-ui/addon/raw-icons/embedded.svg b/packages/boxel-ui/raw-icons/embedded.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/embedded.svg rename to packages/boxel-ui/raw-icons/embedded.svg diff --git a/packages/boxel-ui/addon/raw-icons/exclamation-circle.svg b/packages/boxel-ui/raw-icons/exclamation-circle.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/exclamation-circle.svg rename to packages/boxel-ui/raw-icons/exclamation-circle.svg diff --git a/packages/boxel-ui/addon/raw-icons/exclamation.svg b/packages/boxel-ui/raw-icons/exclamation.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/exclamation.svg rename to packages/boxel-ui/raw-icons/exclamation.svg diff --git a/packages/boxel-ui/addon/raw-icons/eye.svg b/packages/boxel-ui/raw-icons/eye.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/eye.svg rename to packages/boxel-ui/raw-icons/eye.svg diff --git a/packages/boxel-ui/addon/raw-icons/failure-bordered.svg b/packages/boxel-ui/raw-icons/failure-bordered.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/failure-bordered.svg rename to packages/boxel-ui/raw-icons/failure-bordered.svg diff --git a/packages/boxel-ui/addon/raw-icons/field.svg b/packages/boxel-ui/raw-icons/field.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/field.svg rename to packages/boxel-ui/raw-icons/field.svg diff --git a/packages/boxel-ui/addon/raw-icons/file-alert.svg b/packages/boxel-ui/raw-icons/file-alert.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/file-alert.svg rename to packages/boxel-ui/raw-icons/file-alert.svg diff --git a/packages/boxel-ui/addon/raw-icons/file.svg b/packages/boxel-ui/raw-icons/file.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/file.svg rename to packages/boxel-ui/raw-icons/file.svg diff --git a/packages/boxel-ui/addon/raw-icons/fitted.svg b/packages/boxel-ui/raw-icons/fitted.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/fitted.svg rename to packages/boxel-ui/raw-icons/fitted.svg diff --git a/packages/boxel-ui/addon/raw-icons/folder.svg b/packages/boxel-ui/raw-icons/folder.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/folder.svg rename to packages/boxel-ui/raw-icons/folder.svg diff --git a/packages/boxel-ui/addon/raw-icons/form.svg b/packages/boxel-ui/raw-icons/form.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/form.svg rename to packages/boxel-ui/raw-icons/form.svg diff --git a/packages/boxel-ui/addon/raw-icons/four-lines.svg b/packages/boxel-ui/raw-icons/four-lines.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/four-lines.svg rename to packages/boxel-ui/raw-icons/four-lines.svg diff --git a/packages/boxel-ui/addon/raw-icons/grid-3x3.svg b/packages/boxel-ui/raw-icons/grid-3x3.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/grid-3x3.svg rename to packages/boxel-ui/raw-icons/grid-3x3.svg diff --git a/packages/boxel-ui/addon/raw-icons/group.svg b/packages/boxel-ui/raw-icons/group.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/group.svg rename to packages/boxel-ui/raw-icons/group.svg diff --git a/packages/boxel-ui/addon/raw-icons/head.svg b/packages/boxel-ui/raw-icons/head.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/head.svg rename to packages/boxel-ui/raw-icons/head.svg diff --git a/packages/boxel-ui/addon/raw-icons/highlight-icon.svg b/packages/boxel-ui/raw-icons/highlight-icon.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/highlight-icon.svg rename to packages/boxel-ui/raw-icons/highlight-icon.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-circle-selected.svg b/packages/boxel-ui/raw-icons/icon-circle-selected.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-circle-selected.svg rename to packages/boxel-ui/raw-icons/icon-circle-selected.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-circle.svg b/packages/boxel-ui/raw-icons/icon-circle.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-circle.svg rename to packages/boxel-ui/raw-icons/icon-circle.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-code.svg b/packages/boxel-ui/raw-icons/icon-code.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-code.svg rename to packages/boxel-ui/raw-icons/icon-code.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-funnel.svg b/packages/boxel-ui/raw-icons/icon-funnel.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-funnel.svg rename to packages/boxel-ui/raw-icons/icon-funnel.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-globe.svg b/packages/boxel-ui/raw-icons/icon-globe.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-globe.svg rename to packages/boxel-ui/raw-icons/icon-globe.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-grid.svg b/packages/boxel-ui/raw-icons/icon-grid.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-grid.svg rename to packages/boxel-ui/raw-icons/icon-grid.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-hexagon.svg b/packages/boxel-ui/raw-icons/icon-hexagon.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-hexagon.svg rename to packages/boxel-ui/raw-icons/icon-hexagon.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-inherit.svg b/packages/boxel-ui/raw-icons/icon-inherit.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-inherit.svg rename to packages/boxel-ui/raw-icons/icon-inherit.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-link.svg b/packages/boxel-ui/raw-icons/icon-link.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-link.svg rename to packages/boxel-ui/raw-icons/icon-link.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-list.svg b/packages/boxel-ui/raw-icons/icon-list.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-list.svg rename to packages/boxel-ui/raw-icons/icon-list.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-minus-circle.svg b/packages/boxel-ui/raw-icons/icon-minus-circle.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-minus-circle.svg rename to packages/boxel-ui/raw-icons/icon-minus-circle.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-pencil-crossed-out.svg b/packages/boxel-ui/raw-icons/icon-pencil-crossed-out.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-pencil-crossed-out.svg rename to packages/boxel-ui/raw-icons/icon-pencil-crossed-out.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-pencil-not-crossed-out.svg b/packages/boxel-ui/raw-icons/icon-pencil-not-crossed-out.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-pencil-not-crossed-out.svg rename to packages/boxel-ui/raw-icons/icon-pencil-not-crossed-out.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-pencil.svg b/packages/boxel-ui/raw-icons/icon-pencil.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-pencil.svg rename to packages/boxel-ui/raw-icons/icon-pencil.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-plus-circle.svg b/packages/boxel-ui/raw-icons/icon-plus-circle.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-plus-circle.svg rename to packages/boxel-ui/raw-icons/icon-plus-circle.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-plus-thin.svg b/packages/boxel-ui/raw-icons/icon-plus-thin.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-plus-thin.svg rename to packages/boxel-ui/raw-icons/icon-plus-thin.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-plus.svg b/packages/boxel-ui/raw-icons/icon-plus.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-plus.svg rename to packages/boxel-ui/raw-icons/icon-plus.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-search-thick.svg b/packages/boxel-ui/raw-icons/icon-search-thick.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-search-thick.svg rename to packages/boxel-ui/raw-icons/icon-search-thick.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-search.svg b/packages/boxel-ui/raw-icons/icon-search.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-search.svg rename to packages/boxel-ui/raw-icons/icon-search.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-trash.svg b/packages/boxel-ui/raw-icons/icon-trash.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-trash.svg rename to packages/boxel-ui/raw-icons/icon-trash.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-turn-down-right.svg b/packages/boxel-ui/raw-icons/icon-turn-down-right.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-turn-down-right.svg rename to packages/boxel-ui/raw-icons/icon-turn-down-right.svg diff --git a/packages/boxel-ui/addon/raw-icons/icon-x.svg b/packages/boxel-ui/raw-icons/icon-x.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/icon-x.svg rename to packages/boxel-ui/raw-icons/icon-x.svg diff --git a/packages/boxel-ui/addon/raw-icons/image-placeholder.svg b/packages/boxel-ui/raw-icons/image-placeholder.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/image-placeholder.svg rename to packages/boxel-ui/raw-icons/image-placeholder.svg diff --git a/packages/boxel-ui/addon/raw-icons/isolated.svg b/packages/boxel-ui/raw-icons/isolated.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/isolated.svg rename to packages/boxel-ui/raw-icons/isolated.svg diff --git a/packages/boxel-ui/addon/raw-icons/loading-indicator.svg b/packages/boxel-ui/raw-icons/loading-indicator.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/loading-indicator.svg rename to packages/boxel-ui/raw-icons/loading-indicator.svg diff --git a/packages/boxel-ui/addon/raw-icons/lock.svg b/packages/boxel-ui/raw-icons/lock.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/lock.svg rename to packages/boxel-ui/raw-icons/lock.svg diff --git a/packages/boxel-ui/addon/raw-icons/profile.svg b/packages/boxel-ui/raw-icons/profile.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/profile.svg rename to packages/boxel-ui/raw-icons/profile.svg diff --git a/packages/boxel-ui/addon/raw-icons/publish-site-icon.svg b/packages/boxel-ui/raw-icons/publish-site-icon.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/publish-site-icon.svg rename to packages/boxel-ui/raw-icons/publish-site-icon.svg diff --git a/packages/boxel-ui/addon/raw-icons/rows-4.svg b/packages/boxel-ui/raw-icons/rows-4.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/rows-4.svg rename to packages/boxel-ui/raw-icons/rows-4.svg diff --git a/packages/boxel-ui/addon/raw-icons/select-all.svg b/packages/boxel-ui/raw-icons/select-all.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/select-all.svg rename to packages/boxel-ui/raw-icons/select-all.svg diff --git a/packages/boxel-ui/addon/raw-icons/send.svg b/packages/boxel-ui/raw-icons/send.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/send.svg rename to packages/boxel-ui/raw-icons/send.svg diff --git a/packages/boxel-ui/addon/raw-icons/sparkle.svg b/packages/boxel-ui/raw-icons/sparkle.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/sparkle.svg rename to packages/boxel-ui/raw-icons/sparkle.svg diff --git a/packages/boxel-ui/addon/raw-icons/star-filled.svg b/packages/boxel-ui/raw-icons/star-filled.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/star-filled.svg rename to packages/boxel-ui/raw-icons/star-filled.svg diff --git a/packages/boxel-ui/addon/raw-icons/star-half-fill.svg b/packages/boxel-ui/raw-icons/star-half-fill.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/star-half-fill.svg rename to packages/boxel-ui/raw-icons/star-half-fill.svg diff --git a/packages/boxel-ui/addon/raw-icons/star.svg b/packages/boxel-ui/raw-icons/star.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/star.svg rename to packages/boxel-ui/raw-icons/star.svg diff --git a/packages/boxel-ui/addon/raw-icons/success-bordered.svg b/packages/boxel-ui/raw-icons/success-bordered.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/success-bordered.svg rename to packages/boxel-ui/raw-icons/success-bordered.svg diff --git a/packages/boxel-ui/addon/raw-icons/three-dots-horizontal.svg b/packages/boxel-ui/raw-icons/three-dots-horizontal.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/three-dots-horizontal.svg rename to packages/boxel-ui/raw-icons/three-dots-horizontal.svg diff --git a/packages/boxel-ui/addon/raw-icons/triangle-left.svg b/packages/boxel-ui/raw-icons/triangle-left.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/triangle-left.svg rename to packages/boxel-ui/raw-icons/triangle-left.svg diff --git a/packages/boxel-ui/addon/raw-icons/triangle-right.svg b/packages/boxel-ui/raw-icons/triangle-right.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/triangle-right.svg rename to packages/boxel-ui/raw-icons/triangle-right.svg diff --git a/packages/boxel-ui/addon/raw-icons/upload.svg b/packages/boxel-ui/raw-icons/upload.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/upload.svg rename to packages/boxel-ui/raw-icons/upload.svg diff --git a/packages/boxel-ui/addon/raw-icons/warning.svg b/packages/boxel-ui/raw-icons/warning.svg similarity index 100% rename from packages/boxel-ui/addon/raw-icons/warning.svg rename to packages/boxel-ui/raw-icons/warning.svg diff --git a/packages/boxel-ui/rebuild-icons.mjs b/packages/boxel-ui/rebuild-icons.mjs new file mode 100644 index 0000000000..a250af0ffc --- /dev/null +++ b/packages/boxel-ui/rebuild-icons.mjs @@ -0,0 +1,88 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { optimize } from 'svgo'; + +const srcDir = new URL('../raw-icons', import.meta.url).pathname; +const destDir = new URL('../src/icons', import.meta.url).pathname; + +const PREFIX = ` +// This file is auto-generated by 'pnpm rebuild:icons' +import type { TemplateOnlyComponent } from '@ember/component/template-only'; + +import type { Signature } from './types.ts'; + +const IconComponent: TemplateOnlyComponent = ; + +// @ts-expect-error this is the only way to set a name on a Template Only Component currently +IconComponent.name = "__ICON_COMPONENT_NAME__"; +export default IconComponent; +`; +let componentsToGenerate = fs + .readdirSync(srcDir) + .filter((filename) => filename.endsWith('.svg')) + .map((filename) => { + return { + name: toPascalCase(path.parse(filename).name), + sourceFile: filename, + outFile: filename.replace('.svg', '.gts'), + }; + }); +componentsToGenerate.sort((a, b) => a.name.localeCompare(b.name)); + +for (const c of componentsToGenerate) { + console.log(`Generating ${c.sourceFile}...`); + let fullPath = path.resolve(srcDir, c.sourceFile); + let contents = fs.readFileSync(fullPath, 'utf-8'); + contents = optimize(contents, { + path: fullPath, + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeTitle: false, + removeDesc: { removeAny: false }, + removeViewBox: false, + }, + }, + }, + ], + }).data; + contents = contents.replace(//, ''); + let suffix = SUFFIX.replace('__ICON_COMPONENT_NAME__', c.name); + contents = `${PREFIX}${contents}${suffix}`; + fs.writeFileSync(path.resolve(destDir, c.outFile), contents); +} + +let indexContents = `// This file is auto-generated by 'pnpm rebuild:icons' +/* eslint-disable simple-import-sort/imports */ + +import type { Icon, IconComponent } from './icons/types.ts'; + +`; +indexContents += componentsToGenerate + .map((c) => `import ${c.name} from './icons/${c.outFile}';`) + .join('\n'); +indexContents += '\n\n'; +let componentNameArray = componentsToGenerate.map((c) => c.name); +indexContents += `export const ALL_ICON_COMPONENTS = [\n ${componentNameArray.join( + ',\n ', +)},\n];\n`; +indexContents += `export {\n type Icon,\n type IconComponent,\n ${componentNameArray.join( + ',\n ', +)},\n};\n`; +fs.writeFileSync(path.resolve(destDir, '../icons.gts'), indexContents); + +execSync(`prettier -w ${destDir}/*`); + +function toPascalCase(text) { + return text.replace(/(^\w|-\w)/g, clearAndUpper); +} + +function clearAndUpper(text) { + return text.replace(/-/, '').toUpperCase(); +} diff --git a/packages/boxel-ui/rebuild-usage.mjs b/packages/boxel-ui/rebuild-usage.mjs new file mode 100644 index 0000000000..542d6a6016 --- /dev/null +++ b/packages/boxel-ui/rebuild-usage.mjs @@ -0,0 +1,68 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; + +const SRC_DIR = new URL('../src/components', import.meta.url).pathname; +const SRC_FILENAME = 'usage.gts'; +const DEST_FILE = new URL('../src/usage.ts', import.meta.url).pathname; +const PREFIX = ` +// This file is auto-generated by 'pnpm rebuild:usage' +/* eslint-disable simple-import-sort/imports */ +`; + +let componentsToInclude = walk(SRC_DIR) + .flat(Number.POSITIVE_INFINITY) + .filter((filename) => path.parse(filename).base == SRC_FILENAME) + .map((filename) => { + let usageKlassName = filename + .replace(SRC_DIR, '') + .replace('.gts', '') + .split('/') + .filter(Boolean) + .map((s) => toPascalCase(s)) + .join(''); + return { + name: usageKlassName, + componentName: usageKlassName.replace(/Usage$/, ''), + sourceFile: filename, + }; + }); +componentsToInclude.sort((a, b) => a.sourceFile.localeCompare(b.sourceFile)); + +let indexContents = PREFIX + '\n'; +indexContents += componentsToInclude + .map( + (c) => + `import ${c.name} from './components${c.sourceFile.replace( + SRC_DIR, + '', + )}';`, + ) + .join('\n'); +indexContents += '\n\n'; +let componentPairs = componentsToInclude.map( + (c) => `['${c.componentName}', ${c.name}]`, +); +indexContents += `export const ALL_USAGE_COMPONENTS = [\n ${componentPairs.join( + ',\n ', +)}\n];\n`; +// indexContents += `export {\n ${componentNameArray.join(',\n ')}\n};\n`; +fs.writeFileSync(DEST_FILE, indexContents); + +execSync(`prettier -w ${DEST_FILE}`); + +function walk(dirPath) { + let entries = fs.readdirSync(dirPath, { withFileTypes: true }); + return entries.map((entry) => { + const childPath = path.join(dirPath, entry.name); + return entry.isDirectory() ? walk(childPath) : childPath; + }); +} + +function toPascalCase(text) { + return text.replace(/(^\w|-\w)/g, clearAndUpper); +} + +function clearAndUpper(text) { + return text.replace(/-/, '').toUpperCase(); +} diff --git a/packages/boxel-ui/addon/src/components.ts b/packages/boxel-ui/src/components.ts similarity index 89% rename from packages/boxel-ui/addon/src/components.ts rename to packages/boxel-ui/src/components.ts index 84ea2e85f2..4691ff9c87 100644 --- a/packages/boxel-ui/addon/src/components.ts +++ b/packages/boxel-ui/src/components.ts @@ -15,12 +15,11 @@ import ContextButton from './components/context-button/index.gts'; import CopyButton from './components/copy-button/index.gts'; import DateRangePicker from './components/date-range-picker/index.gts'; import DndKanbanBoard, { - type DndItem, DndColumn, } from './components/drag-and-drop/index.gts'; -import BoxelDropdown, { - type DropdownAPI as BoxelDropdownAPI, -} from './components/dropdown/index.gts'; +export { type DndItem } from './components/drag-and-drop/index.gts'; +import BoxelDropdown from './components/dropdown/index.gts'; +export { type DropdownAPI as BoxelDropdownAPI } from './components/dropdown/index.gts'; import EmailInput from './components/email-input/index.gts'; import EntityDisplayWithIcon from './components/entity-icon-display/index.gts'; import EntityDisplayWithThumbnail from './components/entity-thumbnail-display/index.gts'; @@ -32,12 +31,16 @@ import BoxelHeader from './components/header/index.gts'; import Header from './components/header/index.gts'; import IconButton from './components/icon-button/index.gts'; import BoxelInput, { - type InputBottomTreatment as BoxelInputBottomTreatment, - type InputValidationState as BoxelInputValidationState, InputBottomTreatments as BoxelInputBottomTreatments, InputValidationStates as BoxelInputValidationStates, } from './components/input/index.gts'; +export { + type InputBottomTreatment as BoxelInputBottomTreatment, + type InputValidationState as BoxelInputValidationState, +} from './components/input/index.gts'; import BoxelInputGroup from './components/input-group/index.gts'; +import { KanbanColumnConfigSidebar } from './components/kanban/column-config-sidebar.gts'; +import { KanbanDragManager } from './components/kanban/drag.gts'; import { type InsertionPoint, type KanbanColumnConfig, @@ -46,11 +49,9 @@ import { cardsInColumn, columnCount as kanbanColumnCount, findInsertionFromPointer, - KanbanColumnConfigSidebar, - KanbanDragManager, - KanbanPlane, resolveInsertion, -} from './components/kanban/index.gts'; +} from './components/kanban/engine.ts'; +import { KanbanPlane } from './components/kanban/plane.gts'; import Label from './components/label/index.gts'; import LoadingIndicator from './components/loading-indicator/index.gts'; import Menu from './components/menu/index.gts'; @@ -61,7 +62,8 @@ import BoxelMultiSelect, { BoxelMultiSelectBasic, } from './components/multi-select/index.gts'; import PhoneInput from './components/phone-input/index.gts'; -import Picker, { type PickerOption } from './components/picker/index.gts'; +import Picker from './components/picker/index.gts'; +export { type PickerOption } from './components/picker/index.gts'; import Pill from './components/pill/index.gts'; import ProgressBar from './components/progress-bar/index.gts'; import ProgressRadial from './components/progress-radial/index.gts'; @@ -105,13 +107,10 @@ export { BoxelButton, BoxelContainer, BoxelDropdown, - BoxelDropdownAPI, BoxelHeader, BoxelInput, - BoxelInputBottomTreatment, BoxelInputBottomTreatments, BoxelInputGroup, - BoxelInputValidationState, BoxelInputValidationStates, BoxelMessage, BoxelMultiSelect, @@ -129,7 +128,6 @@ export { CopyButton, DateRangePicker, DndColumn, - DndItem, DndKanbanBoard, EmailInput, EntityDisplayWithIcon, @@ -152,7 +150,6 @@ export { Modal, PhoneInput, Picker, - PickerOption, Pill, ProgressBar, ProgressRadial, diff --git a/packages/boxel-ui/addon/src/components/accordion/index.gts b/packages/boxel-ui/src/components/accordion/index.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/accordion/index.gts rename to packages/boxel-ui/src/components/accordion/index.gts diff --git a/packages/boxel-ui/addon/src/components/accordion/item/index.gts b/packages/boxel-ui/src/components/accordion/item/index.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/accordion/item/index.gts rename to packages/boxel-ui/src/components/accordion/item/index.gts diff --git a/packages/boxel-ui/addon/src/components/accordion/usage.gts b/packages/boxel-ui/src/components/accordion/usage.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/accordion/usage.gts rename to packages/boxel-ui/src/components/accordion/usage.gts diff --git a/packages/boxel-ui/addon/src/components/add-button/index.gts b/packages/boxel-ui/src/components/add-button/index.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/add-button/index.gts rename to packages/boxel-ui/src/components/add-button/index.gts diff --git a/packages/boxel-ui/addon/src/components/add-button/usage.gts b/packages/boxel-ui/src/components/add-button/usage.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/add-button/usage.gts rename to packages/boxel-ui/src/components/add-button/usage.gts diff --git a/packages/boxel-ui/addon/src/components/alert/index.gts b/packages/boxel-ui/src/components/alert/index.gts similarity index 98% rename from packages/boxel-ui/addon/src/components/alert/index.gts rename to packages/boxel-ui/src/components/alert/index.gts index bddad9b32c..9585ab7aa3 100644 --- a/packages/boxel-ui/addon/src/components/alert/index.gts +++ b/packages/boxel-ui/src/components/alert/index.gts @@ -4,7 +4,7 @@ import { on } from '@ember/modifier'; import type { ComponentLike } from '@glint/template'; import { cn, eq } from '../../helpers.ts'; -import { FailureBordered, Warning } from '../../icons.gts'; +import { FailureBordered, Warning } from '../../icons.ts'; import Button, { type BoxelButtonKind } from '../button/index.gts'; interface MessagesSignature { diff --git a/packages/boxel-ui/addon/src/components/alert/usage.gts b/packages/boxel-ui/src/components/alert/usage.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/alert/usage.gts rename to packages/boxel-ui/src/components/alert/usage.gts diff --git a/packages/boxel-ui/addon/src/components/avatar/index.gts b/packages/boxel-ui/src/components/avatar/index.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/avatar/index.gts rename to packages/boxel-ui/src/components/avatar/index.gts diff --git a/packages/boxel-ui/addon/src/components/avatar/usage.gts b/packages/boxel-ui/src/components/avatar/usage.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/avatar/usage.gts rename to packages/boxel-ui/src/components/avatar/usage.gts diff --git a/packages/boxel-ui/addon/src/components/basic-fitted/index.gts b/packages/boxel-ui/src/components/basic-fitted/index.gts similarity index 100% rename from packages/boxel-ui/addon/src/components/basic-fitted/index.gts rename to packages/boxel-ui/src/components/basic-fitted/index.gts diff --git a/packages/boxel-ui/addon/src/components/basic-fitted/usage.gts b/packages/boxel-ui/src/components/basic-fitted/usage.gts similarity index 95% rename from packages/boxel-ui/addon/src/components/basic-fitted/usage.gts rename to packages/boxel-ui/src/components/basic-fitted/usage.gts index 7117f7cb41..9dbf9c1af6 100644 --- a/packages/boxel-ui/addon/src/components/basic-fitted/usage.gts +++ b/packages/boxel-ui/src/components/basic-fitted/usage.gts @@ -1,6 +1,7 @@ import Captions from '@cardstack/boxel-icons/captions'; import type { TemplateOnlyComponent } from '@ember/component/template-only'; import { fn } from '@ember/helper'; +import { htmlSafe } from '@ember/template'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import FreestyleUsage from 'ember-freestyle/components/freestyle/usage'; @@ -156,6 +157,12 @@ export default class BasicFittedUsage extends Component { }, ]; + style = (spec: { height: number; width: number }) => { + return htmlSafe( + `container-name: fitted-card; container-type: size; width: ${spec.width}px; height: ${spec.height}px`, + ); + }; +