diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index 42a57b5ecd8..84110c349f0 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -1,6 +1,6 @@ --- name: babysit -description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, keeps it mergeable against staging, triggers Greptile/Cursor Bugbot, fixes real findings, replies to and resolves every thread, and loops until clean +description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, keeps it mergeable against staging, triggers Greptile, fixes real findings, replies to and resolves every thread, and loops until clean --- # Babysit PRs @@ -58,9 +58,9 @@ round. Always check both conditions freshly after every push. 2. **If the PR has a merge conflict**, merge `origin/staging`, resolve the conflicts, run the usual pre-push checks, push, and go to step 8 to re-trigger review. -3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run - automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / - `Greptile Review`) and wait for that first round before doing anything else. +3. **If no review has run yet** (fresh PR, no Greptile comments): Greptile usually runs + automatically on PR open — confirm via `gh pr checks ` (look for `Greptile Review`) and + wait for that first round before doing anything else. 4. **If a review round has landed and it isn't clean**: for every thread where `isResolved: false`, triage the finding on its own merits — this is the part that requires @@ -113,15 +113,13 @@ round. Always check both conditions freshly after every push. rounds; checking sync only before the push (step 6) and never after is how a bad push or a PR whose commit history quietly went stale between rounds goes unnoticed. -8. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR - comments** — never combine them into one comment, each bot only responds to its own mention: +8. **Re-trigger review** by posting `@greptile` as its own PR comment: ```bash gh pr comment --body "@greptile" - gh pr comment --body "@cursor review" ``` 9. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using - a fallback delay of ~250–300s (Greptile/Cursor typically take 1–3 minutes) — never busy-poll + a fallback delay of ~250–300s (Greptile typically takes 1–3 minutes) — never busy-poll in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. @@ -147,7 +145,6 @@ notification email. ## Hard rules -- Never post the two re-review mentions as a single combined comment. - Never paste prod evidence into a reply without scrubbing it first (see above). - Never resolve a thread without replying to it first. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling diff --git a/.agents/skills/v2-api-conventions/SKILL.md b/.agents/skills/v2-api-conventions/SKILL.md index 55da39cd239..44d0b298e6c 100644 --- a/.agents/skills/v2-api-conventions/SKILL.md +++ b/.agents/skills/v2-api-conventions/SKILL.md @@ -113,7 +113,9 @@ Every paged list's binding is declared in `lib/api/contracts/v2/__tests__/list-p Return `nextCursor: null` on the last page and only then. Never construct a cursor client-side. -**Ordering is `sortBy` + `sortOrder`, except where there is nothing to sort by.** Fourteen lists take the pair. Two — `GET /logs` and `GET /workflows/{id}/runs` — have exactly one sortable column (start time), so there is no `sortBy` to pair with and the direction rides on a single `order` param; `sortBy`/`sortOrder` are not accepted there. That split is documented in both contracts and is the *only* sanctioned deviation. A new list picks the pair. Do not "fix" the two by accepting `sortOrder` as an alias: an alias is a second spelling of one thing with undefined precedence when both arrive, which is its own inconsistency, and renaming `order` would break every shipped caller. +**Ordering is `sortBy` + `sortOrder`, except where there is nothing to sort by.** Nearly every paged list takes the pair; `CURSOR_BINDINGS` in `contracts/v2/__tests__/list-pagination.test.ts` is the authoritative set. Exactly one — `GET /workflows/{workflowId}/runs` — has a single sortable column (start time), so there is no `sortBy` to pair with and the direction rides on a single `order` param; `sortBy`/`sortOrder` are not accepted there. That is the *only* sanctioned deviation, and it is documented in its contract. A new list picks the pair. Do not "fix" it by accepting `sortOrder` as an alias: an alias is a second spelling of one thing with undefined precedence when both arrive, which is its own inconsistency. + +`GET /logs` was the second exception until it absorbed `POST /logs/query`. That fold is the cautionary tale for this rule: the justification for the `order` spelling was "logs have exactly one sortable column", and a second endpoint sorting the same rows four ways had already disproved it. When a rule's premise is contradicted by another endpoint on the same collection, fix the premise rather than documenting the exception. **A boolean query param is a real boolean**, declared with `booleanQueryFlagSchema` from `contracts/primitives.ts`. It coerces `'true'`/`'1'` and `'false'`/`'0'`/`''`, so it is a strict widening of a `z.enum(['true','false'])` — which is what two v2 params used to be, purely by inheritance from the internal shapes they reused. Reusing an internal `.shape.x` inherits the internal spelling; re-declare instead when the internal one is not the v2 convention. diff --git a/apps/desktop/e2e/smoke.spec.ts b/apps/desktop/e2e/smoke.spec.ts index 86d3716430e..6e1277dbeb6 100644 --- a/apps/desktop/e2e/smoke.spec.ts +++ b/apps/desktop/e2e/smoke.spec.ts @@ -110,5 +110,18 @@ test.describe('desktop shell smoke', () => { const window = await app.firstWindow() await window.waitForSelector('#retry', { timeout: 30_000 }) expect(window.url().startsWith('file:')).toBe(true) + await expect(window.locator('.wordmark')).toBeVisible() + await expect(window.locator('.wordmark')).toHaveAttribute('aria-label', 'Sim') + await expect(window.locator('#title')).toHaveText('Can’t connect to Sim') + await expect(window.locator('#status')).toHaveText('Check status') + await expect + .poll(() => window.evaluate(() => document.fonts.check('16px "Season Sans"'))) + .toBe(true) + await expect(window.locator('#retry')).toHaveCSS('height', '30px') + await expect(window.locator('#retry')).toHaveCSS('border-radius', '8px') + await expect(window.locator('#retry')).toHaveCSS('padding-left', '8px') + await expect(window.locator('#retry')).toHaveCSS('font-size', '14px') + await expect(window.locator('#retry')).toHaveCSS('line-height', '20px') + await expect(window.locator('#retry')).toHaveCSS('text-align', 'left') }) }) diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index 4f0629d1259..a8b4637f754 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -10,6 +10,10 @@ files: - dist/** - static/** - package.json + - from: ../sim/public/brand/fonts + to: static + filter: + - SeasonSansUprightsVF.woff2 asar: true diff --git a/apps/desktop/static/offline.html b/apps/desktop/static/offline.html index c0a5a93be86..231c415a83e 100644 --- a/apps/desktop/static/offline.html +++ b/apps/desktop/static/offline.html @@ -4,26 +4,27 @@ - Sim — Can’t connect + Sim - Can’t connect +
+ + + + + + + + +
-
S
-

Can’t connect to Sim

-

- Sim couldn’t reach the server. Check your internet connection, then try again. -

-
- -
-