Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
98a8462
fix(server): bound orchestration replay payloads (#8992)
rcawston Sep 2, 2026
50c2588
fix(settings): sync auto-settle and other shared preferences across e…
t3dotgg Sep 2, 2026
2d53d9b
fix(server): prevent accidental service downgrades (#5302)
t3dotgg Sep 2, 2026
5e243cb
fix(claude): preview images read from the workspace (#9119)
t3dotgg Sep 2, 2026
c1b28f0
fix(clients): stop repeating expanded commands (#9120)
t3dotgg Sep 2, 2026
f870bfd
fix(grok): health check, model selection, and stop all work against t…
t3dotgg Sep 2, 2026
5af7a20
feat(desktop): update the desktop app on remote Macs from the Update …
t3dotgg Sep 2, 2026
5e4755d
fix(web): project default model works on the hosted app (#9142)
juliusmarminge Sep 2, 2026
31df0a3
feat(files): open markdown, HTML, and PDF files outside the workspace…
juliusmarminge Sep 2, 2026
288f007
feat(web): render HTML and PDF files in the file viewer (#9143)
juliusmarminge Sep 2, 2026
9720d93
fix(web): browse folders from file breadcrumbs (#8910)
404khai Sep 2, 2026
69c8e64
feat(web): cite assistant responses with inline citations (#9146)
juliusmarminge Sep 2, 2026
d21be18
fix(server): keep citations intact on the outgoing turn
rynfar Sep 3, 2026
fab01c2
fix(desktop): check artifact build prerequisites (#8975)
juliusmarminge Sep 2, 2026
51d6bfa
fix(models): restore sticky new-thread selections (#9164)
maria-rcks Sep 2, 2026
4f33eb9
chore(ci): narrow the UI consistency check-run agent (#9297)
juliusmarminge Sep 3, 2026
d7ee177
chore(ci): only run check-run agents on vouched contributors (#9298)
juliusmarminge Sep 3, 2026
cb10c15
chore(ci): narrow the Effect conventions check-run agent (#9321)
juliusmarminge Sep 3, 2026
950bf65
fix(web): hide deleted providers with prototype keys (#8337)
Lucenx9 Sep 2, 2026
b89039b
fix(server): subscribe before provider settings watcher (#9271)
t3-code[bot] Sep 2, 2026
9fdc2d0
fix(mobile): show filled filter icon on Android when filters are acti…
none23 Sep 2, 2026
b303055
fix(web): warn when shared settings have no target environment (#9207)
imabdulazeez Sep 2, 2026
129717a
fix(desktop): detect installed Spectre libs for Windows builds
juliusmarminge Sep 2, 2026
eb77ce8
feat(web): apply and remove labels from the pull request tab (#9313)
juliusmarminge Sep 3, 2026
5aefb0a
fix(web): let the pull request list use wide screens (#9351)
juliusmarminge Sep 3, 2026
d372b9d
feat(web): mod+w closes the active right panel tab before the window …
juliusmarminge Sep 3, 2026
5b8eee2
feat(web): add PageUp/PageDown chat navigation (#9315)
Yash-Singh1 Sep 3, 2026
7303426
fix(web): settled sidebar rows use the project fallback icon (#9366)
juliusmarminge Sep 3, 2026
44b1a9b
fix(web): match project icon chooser button sizes (#9368)
Yash-Singh1 Sep 3, 2026
bfe19b5
perf(ci): reuse dependency checks in release builds (#9399)
t3dotgg Sep 3, 2026
7f9a778
chore: format the hand-edited adoption files
rynfar Sep 4, 2026
9c3618f
fix(web): keep Prime's background option defaults after #9164
rynfar Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ jobs:
node-version-file: package.json
cache: true
run-install: true
env:
pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata

- id: release_meta
name: Resolve release version
Expand Down Expand Up @@ -203,6 +205,14 @@ jobs:
--current-tag "${{ steps.release_meta.outputs.tag }}" \
--github-output

# Share only the verification results, not the large registry metadata cache.
- name: Upload dependency verification
continue-on-error: true
uses: actions/upload-artifact@v7
with:
name: release-dependency-verification
path: ${{ runner.temp }}/pnpm-metadata/lockfile-verified.jsonl

quality:
name: Release quality checks
needs: [preflight]
Expand Down Expand Up @@ -516,7 +526,18 @@ jobs:
path: ${{ steps.package_cache_path.outputs.path }}
key: windows-release-packages-v1-${{ matrix.arch }}-${{ hashFiles('pnpm-lock.yaml') }}

# pnpm checks the lockfile and policy before reusing this result. A missing
# artifact leaves the cache empty, so installation runs the checks again.
- name: Download dependency verification
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: release-dependency-verification
path: ${{ runner.temp }}/pnpm-metadata

- name: Install desktop dependencies
env:
pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata
run: vp install --filter=@t3tools/desktop... --filter=t3... --filter=@t3tools/scripts...

- name: Cache resource monitor
Expand Down Expand Up @@ -674,6 +695,7 @@ jobs:
# release job actually publishes, or builds go looking in the wrong
# place forever. Falls back to this repository when unset.
PYLON_DESKTOP_UPDATE_REPOSITORY: ${{ vars.PYLON_DESKTOP_UPDATE_REPOSITORY }}
pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata
T3CODE_DESKTOP_REUSE_RESOURCE_MONITOR: ${{ steps.resource_monitor_cache.outputs.cache-hit == 'true' }}
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
Expand Down
92 changes: 36 additions & 56 deletions .macroscope/check-run-agents/effect-service-conventions.md

Large diffs are not rendered by default.

74 changes: 31 additions & 43 deletions .macroscope/check-run-agents/ui-consistency.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,54 @@
---
title: UI Consistency
model: claude-opus-5
effort: high
effort: medium
input: full_diff
tools:
- browse_code
- git_tools
- github_api_read_only
- modify_pr
include:
- "apps/web/src/**/*.ts"
- "apps/web/src/**/*.tsx"
- "apps/web/src/**/*.css"
exclude:
- "apps/web/src/**/*.test.tsx"
labels:
- vouch:trusted
requires:
- Check
maxBudgetPerPR: 25
conclusion: failure
showToolCalls: true
maxBudgetPerRun: 10
---

# UI consistency review

Review changed web UI code and directly affected call sites for consistency with the shared component system, Tailwind ownership, and the behavioral constraints below. Apply these rules when a pull request creates, moves, or modifies controls or styling. Do not demand unrelated repository-wide cleanup.
This is a styling guard for `apps/web`, not a general review. Review only the changed lines in the diff and answer three questions. Do not build the project, inspect emitted CSS, trace selector consumers across the codebase, or ask for screenshots. If the diff does not make a violation obvious, there is no finding.

The goal is not to minimize CSS or class counts at any cost. The goal is to put each behavior in the smallest correct owner while preserving interaction, theming, accessibility, layout, and browser behavior.
## 1. Shared primitives over custom controls

## Shared controls and variants
Product UI must use the primitives in `apps/web/src/components/ui` (`Button`, `Input`, `InputGroup`, `Select`, `Toggle`, `Menu`, `Dialog`, `Tooltip`, `ScrollArea`, and the rest of that directory) instead of rebuilding them.

- Prefer the core UI primitives in `apps/web/src/components/ui` over native controls or locally reconstructed primitives. In ordinary product UI, a raw `<button>` that recreates `Button`, a raw input that recreates `Input` or `InputGroup`, or a local trigger that recreates `Select`, `Toggle`, or `Menu` is a concrete finding.
- Do not flag raw elements that intentionally implement a semantic row, tab, resize handle, swatch, image target, editor surface, or another interaction whose behavior or geometry differs from the core primitive.
- When multiple call sites repeat the same durable geometry or treatment, prefer a named primitive size or variant. Examples include compact controls, micro icon actions, muted ghost actions, and glass actions. Keep contextual layout, width, and color at the call site.
- Flag large call-site class strings that override a primitive's core height, radius, padding, focus ring, cursor, hit target, or base state colors. Prefer extending the primitive contract when the same pattern is genuinely shared.
- Preserve accessibility and interaction semantics during migrations: focus-visible rings, disabled behavior, loading state, keyboard behavior, pointer cursor, `aria-*`, Base UI or Radix render/close props, and coarse-pointer hit targets.
- Do not require tests for a tiny visual-only class migration. Require focused tests when primitive composition changes behavior, prop forwarding, state transitions, keyboard handling, or width/defaulting logic.
- Flag a raw `<button>`, `<input>`, `<select>`, or `<textarea>` styled to look like a control when the matching primitive exists.
- Flag a call site that overrides a primitive's height, radius, padding, focus ring, or base colors with its own class string. If the same look is needed in several places, the fix is a new size or variant on the primitive.
- Do not flag raw elements that are intentionally not a primitive: semantic rows, tabs, resize handles, swatches, editor surfaces, or anything whose behavior or geometry is different by design.

## CSS and Tailwind ownership
## 2. Tailwind in the owning component, not global CSS

- Ordinary one-owner presentation belongs in the owning TS or TSX module as static Tailwind classes or owner-level CSS variables. Examples include local geometry, spacing, typography, backgrounds, borders, simple vendor pseudo-elements, and component-only positioning.
- Keep global CSS when it is genuinely reusable or behaviorally complex: generated markdown or imperative DOM, custom elements and shadow roots, masks, shared or complex pseudo-elements, animations, glass composition, runtime theme variables, safe-area calculations, scrollbar-lane preservation, Electron drag regions, and browser/vendor integration. Simple owner-local pseudo-elements may still belong in the owning module.
- Before calling a selector dead, trace literal, dynamic, generated, imperative, test, custom-element, and shadow-root consumers. Search both the emitted class string and any class-valued field names through their final DOM sink. A helper returning a class is not proof that it is rendered, and a missed downstream property read can make a deletion unsafe.
- Flag duplicate declarations only after comparing cascade layer, selector specificity, inheritance, runtime theme scope, media/variant scope, and the final owning element. Textually identical declarations are not necessarily behaviorally redundant.
- When moving CSS into Tailwind, preserve selector scope and cascade ownership. A utility at the owner is preferable to a fragile global override that depends on stylesheet order.
- Do not request moving complex global behavior into arbitrary Tailwind merely to reduce `index.css`. Do not preserve ordinary one-owner CSS merely because it already exists globally.
Styling lives as Tailwind classes in the component that renders the element.

## Themes and generated CSS
- Flag new rules added to `apps/web/src/index.css` or any other `.css` file when the same styling could be Tailwind classes on the owning component. New global CSS is acceptable only for things Tailwind cannot express at the owner: generated markdown or imperative DOM, custom elements and shadow roots, animations, runtime theme variables, and browser or Electron integration.
- Flag new `style={{ ... }}` objects or inline `<style>` blocks that carry static values a Tailwind class already covers. Dynamic values computed at runtime are fine.
- Flag theme-only declarations that use raw `.dark` selectors instead of `@variant dark` / `@variant light`.

- Use the project variants for theme-only declarations:
- dark-only declarations use `@variant dark`;
- light-only declarations use `@variant light`;
- raw `.dark` should remain only in the `dark` and `light` custom-variant definitions.
- Preserve custom themes and runtime token bridges. Removing a variable or selector is safe only when all runtime, inspector, generated, and theme-palette consumers are accounted for.
- Contrast and accessibility settings that target app chrome must derive from semantic color tokens. Do not apply `filter` to `html`, `body`, or the app root: it also changes user media, previews, terminals, glass backdrop ownership, and view-transition snapshots.
- Preserve alpha and surface ownership when deriving contrast tokens. Soften translucent borders and inputs toward transparent rather than an opaque canvas, use a modest semantic-foreground mix for stronger borders, and adjust card, popover, accent, secondary, and message foregrounds against their own surfaces when the base foreground changes.
- Runtime-adjusted roles must be ordinary custom properties shared by the Tailwind bridge, global CSS, imperative style strings, and bridge snapshots sent to other renderers. Audit literal `var(--foreground)`, `var(--border)`, and related role reads so headings, markdown chrome, menus, previews, and utilities do not split into adjusted and unadjusted colors.
- Inspect emitted production CSS after unusual variants, arbitrary selectors, nested pseudo-elements, or attribute matching. Source syntax that looks valid is insufficient.
- Flag malformed or empty emitted selectors such as empty `:is()` or `:not(:is())`, selector branches that can never match their own class attribute, and transformations that silently drop the intended rule.
- Prefer source-level logic over clever selectors when behavior depends on consumer-provided class strings. Preserve `MenuPopup`'s current defaulting contract: a string `className` containing a `w-*`, `min-w-*`, or `max-w-*` utility after variant prefixes are stripped suppresses `min-w-32`; a string without one and a functional/non-string `className` keep the default. Arbitrary width values count as width utilities, and the consumer class must be merged last so it retains control. Do not replace this with a raw class-attribute substring selector.
- Do not fail solely because a valid emitted selector is verbose or because a source rule uses an intentional custom property.
## 3. Composable components

## Scroll and virtualized lists
The reference for how this codebase wants UI built is the composer banner system in `apps/web/src/components/chat/`:

- `ScrollArea` owns and masks its Base UI viewport. A virtualized list usually owns a native scrolling element and cannot automatically reuse viewport-specific `ScrollArea` behavior.
- Repeated native or virtualized overflow fades should use the shared virtualized-scroll-fade contract rather than component-named mask selectors.
- Preserve runtime top and bottom overflow state. Do not replace dynamic fades with an always-on static mask.
- Preserve fade geometry and keep the native scrollbar lane opaque so the track and thumb stay visible and usable. A visually similar mask that fades the scrollbar is a regression.
- Verify actual scroll behavior when changing virtualizers, masks, overflow ownership, or scrollbar selectors. Source-level class comparison is not enough.
- `ComposerBanner.tsx` exports one object of small slot components (`Root`, `Row`, `Icon`, `Content`, `Actions`, `Dismiss`, `Children`, and so on). Each slot owns its own Tailwind classes, exposes a `data-slot` attribute, takes `className` and spreads the rest of its props, and uses `cn` so callers can adjust without overriding. Variants and density are props on `Root`, not ad hoc class strings at call sites. Where a slot needs a real control it renders `Button` from `ui/` rather than a styled `<button>`.
- `ComposerBannerStack.tsx` composes those slots into the ordered stack and owns only stack behavior (priority, expand and collapse, dismiss transitions).
- Consumers such as `ComposerActivityStatus.tsx`, `ComposerStashBadge.tsx`, and `ComposerPlanFollowUpBanner.tsx` are a few lines of `<ComposerBanner.Row><ComposerBanner.Icon/><ComposerBanner.Content>…` with no styling of their own.

## Visual and layout preservation
Hold new and changed UI to that shape:

- Preserve responsive geometry, titlebar insets, panel and inline-preview modes, desktop Electron layout, light and dark contrast, clipping, radius, and composable shadows.
- For meaningful visual changes, prefer available real-app evidence using the actual component and state. A mock recreation does not validate the real component. Light and dark evidence is useful when theme-sensitive styles change, but missing or inaccessible evidence alone is not a finding; report only a concrete regression supported by the diff, code, or available artifacts.
Expand All @@ -77,9 +62,12 @@ The goal is not to minimize CSS or class counts at any cost. The goal is to put
- Preserve intentional exceptions and comments that explain browser, virtualizer, theme, or Electron constraints.
- If a proposed cleanup cannot prove ownership or semantic equivalence, ask for evidence or leave it unchanged rather than guessing.
- Select verification gates according to the changed behavior: typecheck or focused tests for typing and interaction contracts, production build and emitted-CSS inspection for Tailwind or selector transformations, and real-app evidence for meaningful visual behavior when available. These gates are complementary when applicable, but do not require every gate for tiny visual-only migrations or fail solely because an artifact the configured tools cannot produce is absent.
- Flag a new component that copies a chunk of an existing primitive's markup and classes instead of composing or extending it.
- Flag a large one-off class string on a shared component when it is clearly the same treatment another call site already uses. The fix is a variant or a new slot on the shared component.
- Flag a new composer banner or notice that bypasses `ComposerBanner` slots and hand-builds its row, icon column, or actions.

## Reporting

Report only concrete violations introduced by changed lines or behavior, plus pre-existing behavior that the patch directly makes relevant or worsens. Touching a large file does not make unrelated retained issues reportable. Prefer precise inline comments on the smallest relevant line range. Explain the broken behavior or ownership rule, not merely the preferred syntax, and state the smallest expected fix. A clear consistency or regression risk may fail the check. Do not fail for optional aesthetic preferences, harmless class ordering, or unrelated legacy code.
Report only violations introduced by changed lines. Post each as a brief inline comment on the relevant line: name the rule, name the primitive or location the code should use instead, and stop. Do not comment on class ordering, aesthetic preference, or untouched legacy code, and do not demand cleanup outside the PR's scope.

This check defaults to failure. When there are no findings, stop immediately and make the entire final response exactly `All clear` on one line. Do not add a title, explanation, punctuation, Markdown, JSON, or trailing analysis, and do not continue reasoning after deciding the review is clean.
When there are no findings, make the entire final response exactly `All clear` on one line with nothing else.
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Contributing

## Developer Setup

See the [maintainer scripts guide](docs/internals/scripts.md#first-checkout) for the initial checkout,
development commands, tests, and platform-specific desktop packaging prerequisites.

## Read This First

We are not actively accepting contributions right now.
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts";
import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
import * as DesktopShellEnvironment from "../shell/DesktopShellEnvironment.ts";
import * as DesktopState from "./DesktopState.ts";
import * as DesktopRemoteUpdates from "../updates/DesktopRemoteUpdates.ts";
import * as DesktopUpdates from "../updates/DesktopUpdates.ts";
import * as DesktopWslBackend from "../wsl/DesktopWslBackend.ts";

Expand Down Expand Up @@ -292,6 +293,7 @@ const startup = Effect.gen(function* () {
yield* appIdentity.configure;
yield* applicationMenu.configure;
yield* updates.configure;
yield* DesktopRemoteUpdates.listen;
yield* linuxUrlHandler.register;
yield* bootstrap.pipe(Effect.catchCause((cause) => fatalStartupCause("bootstrap", cause)));
}).pipe(Effect.withSpan("desktop.startup"));
Expand Down
11 changes: 10 additions & 1 deletion apps/desktop/src/app/DesktopLifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ describe("DesktopLifecycle", () => {
for (const platform of ["darwin", "win32", "linux"] satisfies ReadonlyArray<NodeJS.Platform>) {
it.effect(`lets the updater's quit event proceed on ${platform}`, () => {
const appListeners = new Map<string, (...args: readonly unknown[]) => void>();
let windowsDestroyed = false;
const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
platform,
isDevelopment: false,
Expand All @@ -110,7 +111,13 @@ describe("DesktopLifecycle", () => {
const layer = DesktopLifecycle.layer.pipe(
Layer.provideMerge(makeElectronAppLayer(appListeners)),
Layer.provideMerge(electronThemeLayer),
Layer.provideMerge(makeElectronWindowLayer()),
Layer.provideMerge(
makeElectronWindowLayer(
Effect.sync(() => {
windowsDestroyed = true;
}),
),
),
Layer.provideMerge(makeDesktopWindowLayer()),
Layer.provideMerge(environmentLayer),
Layer.provideMerge(DesktopShutdown.layer),
Expand All @@ -123,6 +130,7 @@ describe("DesktopLifecycle", () => {
yield* lifecycle.register;

appListeners.get("before-quit-for-update")?.();
yield* Effect.yieldNow;

let prevented = false;
const event = {
Expand All @@ -136,6 +144,7 @@ describe("DesktopLifecycle", () => {
prevented,
"cancelling this event prevents the updater from completing its relaunch",
);
assert.isTrue(windowsDestroyed);

const state = yield* DesktopState.DesktopState;
assert.isTrue(yield* Ref.get(state.quitting));
Expand Down
13 changes: 11 additions & 2 deletions apps/desktop/src/app/DesktopLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export const make = DesktopLifecycle.of({
}),
register: Effect.gen(function* () {
const desktopWindow = yield* DesktopWindow.DesktopWindow;
const electronWindow = yield* ElectronWindow.ElectronWindow;
const electronApp = yield* ElectronApp.ElectronApp;
const electronTheme = yield* ElectronTheme.ElectronTheme;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
Expand All @@ -206,8 +207,16 @@ export const make = DesktopLifecycle.of({
// Cancelling the following app "before-quit" event breaks that sequence,
// most visibly on macOS where the native updater performs the relaunch.
updaterQuitAllowed = true;
void runEffect(
logLifecycleInfo("allowing updater-controlled quit").pipe(
// This event is synchronous and the updater's quit proceeds as soon as
// the listener returns, so a forked destroyAll would race the quit
// and windows could still be open when the process exits (visible on
// macOS). Destroy them inline.
Effect.runSyncWith(context)(
electronWindow.destroyAll.pipe(
Effect.andThen(logLifecycleInfo("allowing updater-controlled quit")),
Effect.catchCause((cause) =>
logLifecycleError("failed to destroy windows before updater quit", { cause }),
),
Effect.withSpan("desktop.lifecycle.beforeQuitForUpdate"),
),
);
Expand Down
Loading
Loading