Skip to content

Commit 82f0f60

Browse files
committed
Merge remote-tracking branch 'upstream/main' into sync-upstream-main-20260625
2 parents b54df26 + 22f021e commit 82f0f60

1,312 files changed

Lines changed: 98357 additions & 80268 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
# T3CODE_CLERK_JWT_TEMPLATE=t3-relay
99
# T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=oauthapp_...
1010

11+
# Optional: signed macOS passkey builds. The RP domain defaults to the Frontend API
12+
# hostname encoded in T3CODE_CLERK_PUBLISHABLE_KEY. Set the override only when Clerk
13+
# returns a different RP ID or when multiple domains must be entitled.
14+
# T3CODE_APPLE_TEAM_ID=ABC1234567
15+
# T3CODE_MACOS_PROVISIONING_PROFILE=/absolute/path/to/t3code.provisionprofile
16+
# T3CODE_CLERK_PASSKEY_RP_DOMAINS=example.clerk.accounts.dev,clerk.example.com
17+
1118
# Get this from your relay deployment. `infra/relay` deploys update it automatically.
1219
# T3CODE_RELAY_URL=https://relay.example.com
1320

.github/workflows/ci.yml

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
run: |
3939
test -f apps/desktop/dist-electron/preload.cjs
4040
grep -nE "desktopBridge|getLocalEnvironmentBootstrap|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.cjs
41+
grep -n "__clerk_internal_electron_passkeys" apps/desktop/dist-electron/preload.cjs
4142
4243
test:
4344
name: Test
@@ -60,53 +61,6 @@ jobs:
6061
- name: Test
6162
run: vp run test
6263

63-
test_browser:
64-
name: Test Browser
65-
if: false
66-
runs-on: t3code-arc
67-
timeout-minutes: 20
68-
steps:
69-
- name: Checkout
70-
uses: actions/checkout@v6
71-
72-
- name: Setup Vite+
73-
uses: voidzero-dev/setup-vp@v1
74-
with:
75-
node-version-file: package.json
76-
cache: true
77-
run-install: true
78-
79-
- name: Cache Playwright browsers
80-
uses: actions/cache@v5
81-
with:
82-
path: ~/.cache/ms-playwright
83-
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
84-
restore-keys: |
85-
${{ runner.os }}-playwright-
86-
87-
- name: Install browser test runtime
88-
run: vp run --filter @t3tools/web test:browser:install
89-
90-
- name: Browser test / Chat view
91-
working-directory: apps/web
92-
run: vp test run --mode browser --browser=chromium src/components/ChatView.browser.tsx
93-
94-
- name: Browser test / Chat markdown
95-
working-directory: apps/web
96-
run: vp test run --mode browser --browser=chromium src/components/ChatMarkdown.browser.tsx
97-
98-
- name: Browser test / Components
99-
working-directory: apps/web
100-
run: |
101-
vp test run --mode browser --browser=chromium \
102-
src/components/GitActionsControl.browser.tsx \
103-
src/components/KeybindingsToast.browser.tsx \
104-
src/components/ThreadTerminalDrawer.browser.tsx \
105-
src/components/chat/MessagesTimeline.browser.tsx \
106-
src/components/chat/ProviderModelPicker.browser.tsx \
107-
src/components/chat/CompactComposerControlsMenu.browser.tsx \
108-
src/components/settings/SettingsPanels.browser.tsx
109-
11064
mobile_native_static_analysis:
11165
name: Mobile Native Static Analysis
11266
runs-on: macos-15

.github/workflows/mobile-eas-preview.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ name: Mobile EAS Preview
22

33
on:
44
pull_request:
5+
types: [opened, reopened, synchronize, labeled, unlabeled]
56

67
jobs:
78
preview:
89
name: EAS Preview
9-
if: github.repository_owner == 'pingdotgg'
10-
runs-on: t3code-arc
10+
if: github.repository_owner == 'pingdotgg' && contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment')
11+
runs-on: blacksmith-8vcpu-ubuntu-2404
1112
permissions:
1213
contents: read
1314
pull-requests: write

.github/workflows/release.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,9 @@ jobs:
517517
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
518518
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
519519
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
520+
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
521+
MACOS_PROVISIONING_PROFILE: ${{ secrets.MACOS_PROVISIONING_PROFILE }}
522+
T3CODE_CLERK_PASSKEY_RP_DOMAINS: ${{ vars.CLERK_PASSKEY_RP_DOMAINS }}
520523
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
521524
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
522525
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
@@ -545,9 +548,21 @@ jobs:
545548
if [[ "${{ matrix.platform }}" == "mac" ]]; then
546549
if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD"; then
547550
if has_all "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then
551+
if ! has_all "$APPLE_TEAM_ID" "$MACOS_PROVISIONING_PROFILE"; then
552+
echo "macOS notarization is configured, but APPLE_TEAM_ID or MACOS_PROVISIONING_PROFILE is missing." >&2
553+
exit 1
554+
fi
555+
548556
key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
549557
printf '%s' "$APPLE_API_KEY" > "$key_path"
550558
export APPLE_API_KEY="$key_path"
559+
560+
profile_path="$RUNNER_TEMP/t3code.provisionprofile"
561+
printf '%s' "$MACOS_PROVISIONING_PROFILE" | base64 -D > "$profile_path"
562+
security cms -D -i "$profile_path" >/dev/null
563+
export T3CODE_APPLE_TEAM_ID="$APPLE_TEAM_ID"
564+
export T3CODE_MACOS_PROVISIONING_PROFILE="$profile_path"
565+
551566
echo "macOS notarization enabled."
552567
else
553568
unset APPLE_API_KEY APPLE_API_KEY_ID APPLE_API_ISSUER
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
title: Effect Service Conventions
3+
model: claude-opus-4-8
4+
effort: high
5+
input: full_diff
6+
tools:
7+
- browse_code
8+
- git_tools
9+
- github_api_read_only
10+
- modify_pr
11+
include:
12+
- "apps/**/*.ts"
13+
- "apps/**/*.tsx"
14+
- "packages/**/*.ts"
15+
- "packages/**/*.tsx"
16+
- "infra/**/*.ts"
17+
- "infra/**/*.tsx"
18+
conclusion: failure
19+
showToolCalls: true
20+
---
21+
22+
# Effect service review
23+
24+
Review changed TypeScript and directly affected call sites for the conventions below. Apply them when a pull request creates, moves, refactors, or consumes an Effect service. Do not demand unrelated repository-wide cleanup. Treat these instructions as authoritative when older code differs.
25+
26+
## Imports and module namespaces
27+
28+
- Import Effect library modules from their subpaths as namespaces, for example `import * as Effect from "effect/Effect"` and `import * as Layer from "effect/Layer"`. Flag consolidated named imports from `"effect"` in touched Effect service code.
29+
- At a service boundary, import the local service module as a namespace and use its public module shape: `WorkspacePaths.WorkspacePaths`, `WorkspacePaths.make`, and `WorkspacePaths.layer`. Flag aliases such as `import { layer as workspacePathsLayer }` that erase the module namespace.
30+
- Namespace imports are not a blanket rule. Keep named imports for whole packages such as `@t3tools/contracts`, and for modules used only for a pure helper, error, schema, config value, or standalone type. Do not request `import type * as Contracts`.
31+
- A package subpath that is itself a service module may use a namespace import when callers access its service/tag, `make`, or `layer` members.
32+
- When a barrel exposes an entire service module, prefer `export * as TokenStore from "./tokenStore.ts"` so consumers can use `TokenStore.TokenStore` and `TokenStore.layer`. Do not individually rename `make` and `layer` exports to simulate a namespace.
33+
34+
## Service definition
35+
36+
- Use the canonical single-file order: imports, error/schema declarations, the `Context.Service` tag with its inline interface, `make`, then `layer`.
37+
- Keep a service's schemas/errors, `Context.Service` tag, construction, and layer in one canonical module when they form one implementation.
38+
- Define the service interface inline in the `Context.Service` declaration. Do not retain a standalone `FooShape` or `FooServiceShape` interface/type.
39+
- Refer to the inferred service interface as `Foo["Service"]`, including in mechanically updated orchestration, MCP, tests, and integration harnesses.
40+
- Export a real `make` when the module owns construction. Do not create `make = Effect.succeed(...)` solely to force `Layer.effect`.
41+
- Export the canonical layer as `export const layer = Layer...`. `Layer.effect` is not required: use `Layer.succeed`, `Layer.scoped`, or another appropriate constructor when that matches the implementation.
42+
- In a concrete implementation module already named for the implementation, use plain `make` and `layer` (for example `BunPtyAdapter.ts` and `NodePtyAdapter.ts`).
43+
- Keep implementation-specific names when an abstract port module contains one of several possible implementations, for example `makeCloudflaredRelayClient` and `layerCloudflared` in `RelayClient.ts`.
44+
- `infra/relay/src/db.ts` is an intentional exception: an inline `Layer.succeed(RelayDb, db)` is acceptable without generic `make`/`layer` exports.
45+
46+
## Errors and predicates
47+
48+
- Define service failures with `Schema.TaggedErrorClass` and structured attributes. Derive `message` from those attributes rather than storing an unstructured message as the only data.
49+
- `Schema.Defect()` is not a substitute for modeling a generic error: its tag, fields, or both must identify the failure structurally, and its `message` must not merely stringify an opaque cause. A semantically precise error tag may preserve a real `cause` without inventing a redundant singleton field when no additional variable context exists; still retain any real path, resource, request, or entity context available at the wrapping site.
50+
- Capture stable, serializable domain context such as the operation or stage, resource/path or entity identifier, and normalized category/status. Map failures where that context is known instead of wrapping an entire multi-step pipeline in one generic error. Do not add a `detail` field that merely copies `cause.message` and then use it to construct the wrapper message.
51+
- Keep direct error attributes and log annotations safe and bounded. Do not copy raw wire payloads, command arguments or output, signed URLs, credentials, query strings, fragments, selectors, or arbitrary defect text into `detail`, `reason`, `message`, or a parallel log payload. Preserve the exact underlying value only as `cause`; expose normalized categories plus lengths/counts and safe URL protocol/hostname diagnostics where useful. Logging a sanitized error must not reintroduce a removed legacy `detail` or serialized `cause` field beside it.
52+
- When translating or wrapping a real failure, preserve the immediate underlying error itself as `cause` alongside the structural fields so the complete error chain and stack remain available. If every construction wraps a failure, `cause` should be required; make it optional only when the same error can legitimately originate without an underlying failure.
53+
- At a translation boundary, pass through an already structured domain error when it is part of the declared target error channel. Wrap only unknown or genuinely lower-level failures. A static factory or mapper may perform this classification when it is reused and keeps the policy next to the target error type.
54+
- Derive the wrapper's `message` exclusively from its stable structural attributes, never from `cause`, `cause.message`, or a stringified defect. Do not replace the immediate error with only `error.cause`, erase a structured upstream error into a string, or manufacture an `Error` merely to populate `cause`. Pure validation/domain errors created without an underlying failure do not need a cause.
55+
- Do not encode the same distinction twice with both a specific error tag and a single-value `operation`, `reason`, `kind`, or `phase` literal. Choose one coherent model: use distinct error classes and omit the redundant discriminator when callers or messages treat the failures as genuinely different, or use one service-level error with a multi-value operation discriminator and a generic message derived from that operation when the failures share the same semantics.
56+
- Treat an error message exposed through an HTTP/RPC response, persisted state, UI, or another caller-visible boundary as behavior. Preserve those messages during a structural refactor. Existing distinct caller-visible messages are evidence that the failures should normally remain distinct error tags without redundant singleton discriminators, rather than being collapsed into a generic operation error.
57+
- Split semantically distinct failures into separate error classes when a `reason`, `kind`, `phase`, or similar discriminator is used to choose the user-facing message or drive caller control flow. A discriminator used only for internal diagnostics may remain a field.
58+
- Use `Schema.Union` of error classes when a shared schema, predicate, or helper type is useful.
59+
- Export direct schema predicates such as `export const isFoo = Schema.is(Foo)`. Flag a private `Schema.is` constant wrapped by a redundant function with the same signature.
60+
- Do not introduce a large `switch` or lookup table in an error's `message` getter to model failures that deserve separate error classes.
61+
- Catch statically known tagged failures with `Effect.catchTags({ ... })`, including when handling only one tag. Do not use `catchIf` with a schema predicate merely to recover one or more known `_tag` variants, and do not use `catchTag`. `Effect.catch` is appropriate when the entire error channel is intentionally handled; `catchIf` remains appropriate for genuinely structural predicates such as inspecting an underlying platform error code.
62+
- Do not add a helper whose only behavior is `(...args) => new SomeError({ ...args })`, including curried aliases used once with `mapError`. Construct the error at the failure boundary so its attributes and cause remain visible. Keep a mapper only when it performs real normalization, passes through existing domain errors, or adds reusable context/control flow.
63+
- When a reusable error-to-error translation clearly belongs to the target error type, prefer a descriptive static factory on that error class over a detached production-side switch. Do not force a static method for one-off inline mappings.
64+
65+
## File layout and migrations
66+
67+
- When combining `domain/Services/Foo.ts` and `domain/Layers/Foo.ts`, hoist the result to `domain/Foo.ts`.
68+
- Delete the old service/layer files. Do not leave compatibility re-export shims. Mechanically update every consumer, including orchestration, MCP, tests, and integration harnesses, to the canonical path.
69+
- Do not flag genuinely separate implementation/adapter modules merely because they remain in an implementation-oriented directory.
70+
- Avoid substantive orchestration or MCP redesign in service-cleanup PRs. Mechanical import, layer, and `Service["Service"]` updates are expected when required to remove obsolete paths or shapes.
71+
72+
## Change discipline
73+
74+
- Preserve useful comments, invariants, and specification documentation while moving code.
75+
- Do not add large tests solely to prove a mechanical refactor. Update existing tests and imports as needed.
76+
- If backend behavior changes, require focused tests. Use test implementations/layers for external services only; do not mock out core business logic.
77+
- Do not require `Layer.effect`, universal namespace imports, generic `make`/`layer` names for abstract-port implementations, separate error classes for diagnostic-only fields, or new tests for import-only changes.
78+
79+
## Reporting
80+
81+
Report only concrete violations introduced or retained in the pull request's changed scope. Prefer precise inline comments on the smallest relevant line range and state the expected fix. A clear convention violation may fail the check. Do not fail for optional style preferences or unrelated legacy code.
82+
83+
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.

apps/desktop/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"smoke-test": "node scripts/smoke-test.mjs"
1313
},
1414
"dependencies": {
15+
"@clerk/electron": "catalog:",
16+
"@clerk/electron-passkeys": "catalog:",
1517
"@effect/platform-node": "catalog:",
1618
"@t3tools/client-runtime": "workspace:*",
1719
"@t3tools/contracts": "workspace:*",
@@ -20,6 +22,7 @@
2022
"@t3tools/tailscale": "workspace:*",
2123
"effect": "catalog:",
2224
"electron": "41.5.0",
25+
"electron-store": "^8.2.0",
2326
"electron-updater": "^6.6.2",
2427
"playwright-core": "1.60.0",
2528
"react-grab": "^0.1.32"
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import { readFile, writeFile } from "node:fs/promises";
2-
import { createRequire } from "node:module";
3-
import { dirname, join } from "node:path";
4-
import { fileURLToPath } from "node:url";
1+
import * as NodeFSP from "node:fs/promises";
2+
import * as NodeModule from "node:module";
3+
import * as NodePath from "node:path";
4+
import * as NodeURL from "node:url";
55

66
import { compile } from "tailwindcss";
77

8-
const directory = dirname(fileURLToPath(import.meta.url));
9-
const appRoot = join(directory, "..");
10-
const sourcePath = join(appRoot, "src", "preview", "Annotation.css");
11-
const preloadPath = join(appRoot, "src", "preview", "PickPreload.ts");
12-
const outputPath = join(appRoot, "src", "preview", "AnnotationStyles.generated.ts");
13-
const require = createRequire(import.meta.url);
14-
const tailwindRoot = dirname(require.resolve("tailwindcss/package.json"));
8+
const directory = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
9+
const appRoot = NodePath.join(directory, "..");
10+
const sourcePath = NodePath.join(appRoot, "src", "preview", "Annotation.css");
11+
const preloadPath = NodePath.join(appRoot, "src", "preview", "PickPreload.ts");
12+
const outputPath = NodePath.join(appRoot, "src", "preview", "AnnotationStyles.generated.ts");
13+
const require = NodeModule.createRequire(import.meta.url);
14+
const tailwindRoot = NodePath.dirname(require.resolve("tailwindcss/package.json"));
1515

1616
const [annotationSource, preloadSource, themeSource, preflightSource] = await Promise.all([
17-
readFile(sourcePath, "utf8"),
18-
readFile(preloadPath, "utf8"),
19-
readFile(join(tailwindRoot, "theme.css"), "utf8"),
20-
readFile(join(tailwindRoot, "preflight.css"), "utf8"),
17+
NodeFSP.readFile(sourcePath, "utf8"),
18+
NodeFSP.readFile(preloadPath, "utf8"),
19+
NodeFSP.readFile(NodePath.join(tailwindRoot, "theme.css"), "utf8"),
20+
NodeFSP.readFile(NodePath.join(tailwindRoot, "preflight.css"), "utf8"),
2121
]);
2222

2323
const candidates = new Set(
@@ -37,4 +37,4 @@ const encodedCss = `'${css
3737
.replaceAll("\n", "\\n")}'`;
3838
const moduleSource = `// Generated by scripts/build-preview-annotation-css.mjs. Do not edit.\nexport const previewAnnotationStyles =\n ${encodedCss};\n`;
3939

40-
await writeFile(outputPath, moduleSource);
40+
await NodeFSP.writeFile(outputPath, moduleSource);

0 commit comments

Comments
 (0)