diff --git a/.github/scripts/ci-changes.cjs b/.github/scripts/ci-changes.cjs index 77a526076de4d..ed32670755ff6 100644 --- a/.github/scripts/ci-changes.cjs +++ b/.github/scripts/ci-changes.cjs @@ -14,7 +14,7 @@ const INERT = [ // Shared test setup and vendored mobile dependencies affect every package. const RUNS_EVERYTHING = [ - /^packages\/shared\/src\/(HostProcess|testing\/longTempDir)\.ts$/, + /^packages\/shared\/src\/(HostProcess|testing\/(longTempDir|providerEnv))\.ts$/, /^apps\/mobile\/deps\//, ]; @@ -27,6 +27,8 @@ const EXTRA_OWNERS = [ [/^native\/browser-secret\//, ["@t3tools/desktop"]], [/^native\/kde-snap-shot\//, ["@t3tools/desktop"]], [/^native\/libghostty-vt\//, ["@t3tools/web"]], + // apps/server/src/cloud/ConnectReachability.test.ts checks the guide's headings. + [/^docs\/user\/t3-connect-troubleshooting\.md$/, ["t3"]], ]; const RUST = /^native\/(resource-monitor|kde-snap-shot|hyprland-snap-shot)\//; @@ -92,11 +94,11 @@ function readWorkspace() { // [] means inert; null means unknown and requires every job. function ownersOf(file, packages) { - if (INERT.some((pattern) => pattern.test(file))) return []; + const extra = EXTRA_OWNERS.flatMap(([pattern, names]) => (pattern.test(file) ? names : [])); + if (INERT.some((pattern) => pattern.test(file))) return extra; // A manifest can change dependencies or versions other packages read. if (path.posix.basename(file) === "package.json") return null; if (RUNS_EVERYTHING.some((pattern) => pattern.test(file))) return null; - const extra = EXTRA_OWNERS.flatMap(([pattern, names]) => (pattern.test(file) ? names : [])); // scripts/build-desktop-artifact.test.ts reads the crates' sources. if (RUST.test(file)) return ["@t3tools/scripts", ...extra]; diff --git a/.github/scripts/thread-transfer-report.cjs b/.github/scripts/thread-transfer-report.cjs index ca57769ed490c..09b25131e4c48 100644 --- a/.github/scripts/thread-transfer-report.cjs +++ b/.github/scripts/thread-transfer-report.cjs @@ -300,18 +300,28 @@ async function resolve({ github, context, core }) { core.setOutput("publish", "false"); return; } - const workflowRuns = await github.paginate(github.rest.actions.listWorkflowRuns, { - owner, - repo, - workflow_id: source.workflow_id, - branch: pull.base.ref, - event: "push", - status: "success", - per_page: 100, - }); + // One page each for the exact base and the newest runs; paging through + // every successful main run costs up to ten requests to keep 20. + const listRuns = async (filters) => { + const { data } = await github.rest.actions.listWorkflowRuns({ + owner, + repo, + workflow_id: source.workflow_id, + branch: pull.base.ref, + event: "push", + status: "success", + per_page: 20, + ...filters, + }); + return data.workflow_runs; + }; + const [baseRuns, recentRuns] = await Promise.all([ + listRuns({ head_sha: pull.base.sha }), + listRuns({}), + ]); const orderedRuns = [ - ...workflowRuns.filter((run) => run.head_sha === pull.base.sha), - ...workflowRuns.filter((run) => run.head_sha !== pull.base.sha), + ...baseRuns, + ...recentRuns.filter((run) => run.head_sha !== pull.base.sha), ].slice(0, 20); let baselineRun; diff --git a/.github/scripts/thread-transfer-report.test.cjs b/.github/scripts/thread-transfer-report.test.cjs index 753c3eb759072..8c4595ccf0c6a 100644 --- a/.github/scripts/thread-transfer-report.test.cjs +++ b/.github/scripts/thread-transfer-report.test.cjs @@ -55,7 +55,18 @@ test("validates the fixed artifact schema", () => { test("resolves a fallback PR with a redacted head repo and exact main baseline", async () => { const outputs = {}; const listWorkflowRunArtifacts = () => {}; - const listWorkflowRuns = () => {}; + // The exact base run sits far past the newest page of main CI runs. + const mainRuns = Array.from({ length: 1_000 }, (_, index) => ({ + id: 1_000 - index, + head_sha: index === 900 ? "base-sha" : `main-sha-${index}`, + })); + const listWorkflowRuns = async ({ head_sha, per_page }) => ({ + data: { + workflow_runs: mainRuns + .filter((run) => head_sha === undefined || run.head_sha === head_sha) + .slice(0, per_page), + }, + }); const listPullRequestsAssociatedWithCommit = () => {}; const github = { paginate: async (method, input) => { @@ -77,9 +88,6 @@ test("resolves a fallback PR with a redacted head repo and exact main baseline", }, ]; } - if (method === listWorkflowRuns) { - return [{ id: 1, head_sha: "base-sha" }]; - } throw new Error("unexpected pagination call"); }, rest: { @@ -123,7 +131,7 @@ test("resolves a fallback PR with a redacted head repo and exact main baseline", assert.equal(outputs.publish, "true"); assert.equal(outputs.pull_number, "5350"); assert.equal(outputs.pr_artifact, "true"); - assert.equal(outputs.baseline_run_id, "1"); + assert.equal(outputs.baseline_run_id, "100"); assert.equal(outputs.baseline_matches_base, "true"); }); diff --git a/.github/triage/PLAYBOOK.md b/.github/triage/PLAYBOOK.md index 78a0156d1eaa1..cc922de448005 100644 --- a/.github/triage/PLAYBOOK.md +++ b/.github/triage/PLAYBOOK.md @@ -125,10 +125,16 @@ of `main` for that work, never the tag-pinned diagnosis clone. - Show the user the complete final issue text and get an explicit yes before posting. Never post without it. - Note at the end of the issue which model and agent produced it. -- If `gh` is not authenticated, offer `gh auth login`, or build a prefilled - https://github.com/pingdotgg/t3code/issues/new URL with title and body query - parameters; print the URL, and open it in their browser only after they - approve. +- Post with `gh issue create --repo pingdotgg/t3code --label via-triage` only + if `gh api repos/pingdotgg/t3code --jq .permissions.triage` prints `true`. +- Otherwise, or if `gh` is not authenticated, build a prefilled + https://github.com/pingdotgg/t3code/issues/new?template=via-triage.yml URL. + The form applies the label whoever submits it. Add `title` plus one + URL-encoded query parameter per form field id: `what-happened`, `diagnosis`, + `steps`, `version`, `environment`, `evidence`, `related`, `workaround` and + `agent`. Keep the full encoded URL below 6,000 characters by shortening any + fields as needed. Give the user any omitted text to paste into the form before + submitting. Print the URL, and open it in their browser only after they approve. - If the user pasted screenshots, remind them to drag the images into the issue after it is created; they cannot be attached from here. diff --git a/.github/workflows/mobile-eas-production.yml b/.github/workflows/mobile-eas-production.yml index 507e013bf1c2a..6877f5c831a08 100644 --- a/.github/workflows/mobile-eas-production.yml +++ b/.github/workflows/mobile-eas-production.yml @@ -215,7 +215,7 @@ jobs: git config user.name "${APP_SLUG}[bot]" git config user.email "${user_id}+${APP_SLUG}[bot]@users.noreply.github.com" git add apps/mobile/app.config.ts - git commit \ + git commit --no-verify \ -m "chore(mobile): bump app version to $RELEASE_VERSION" \ -m "Co-authored-by: codex " git push origin "HEAD:refs/heads/${GITHUB_REF_NAME}" diff --git a/.github/workflows/pr-vouch.yml b/.github/workflows/pr-vouch.yml index c4abb08b727b2..75e2ee8a56548 100644 --- a/.github/workflows/pr-vouch.yml +++ b/.github/workflows/pr-vouch.yml @@ -20,6 +20,10 @@ permissions: jobs: collect-targets: name: Collect PR targets + # Most comments are not recheck commands; skip them before a runner starts. + if: >- + github.event_name != 'issue_comment' || + (github.event.issue.pull_request && contains(github.event.comment.body, '/recheck-vouch')) runs-on: ubuntu-24.04 outputs: targets: ${{ steps.collect.outputs.targets }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d89b7c7abe7a..82f39f8034cc4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -395,8 +395,12 @@ jobs: (github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true') runs-on: blacksmith-8vcpu-ubuntu-2404 timeout-minutes: 5 + # Previews build any branch, but production admits only main and v* tags. + # The empty name runs previews with no environment, so their Clerk and relay + # ids come from .env.example. Everything else matches nightly: the tracing + # read below needs only repository-level Cloudflare credentials. environment: - name: production + name: ${{ inputs.channel != 'preview' && 'production' || '' }} outputs: clerk_publishable_key: ${{ steps.public_config.outputs.clerk_publishable_key }} clerk_jwt_template: ${{ steps.public_config.outputs.clerk_jwt_template }} @@ -462,9 +466,19 @@ jobs: - id: public_config name: Resolve production relay public config shell: bash + env: + PREVIEW: ${{ inputs.channel == 'preview' }} run: | set -euo pipefail + if [[ "$PREVIEW" == "true" ]]; then + env_example() { sed -n "s|^T3CODE_$1=||p" .env.example; } + CLERK_PUBLISHABLE_KEY="$(env_example CLERK_PUBLISHABLE_KEY)" + CLERK_JWT_TEMPLATE="$(env_example CLERK_JWT_TEMPLATE)" + CLERK_CLI_OAUTH_CLIENT_ID="$(env_example CLERK_CLI_OAUTH_CLIENT_ID)" + RELAY_DOMAIN="$(sed -n 's|^T3CODE_RELAY_URL=https://||p' .env.example)" + fi + relay_domain="${RELAY_DOMAIN:-}" if [[ -z "$relay_domain" && -n "${RELAY_API_ZONE_NAME:-}" ]]; then relay_domain="relay.$RELAY_API_ZONE_NAME" @@ -1388,7 +1402,7 @@ jobs: git config user.email "${{ steps.app_bot.outputs.email }}" git add apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json pnpm-lock.yaml - git commit -m "chore(release): prepare $RELEASE_TAG" + git commit --no-verify -m "chore(release): prepare $RELEASE_TAG" git push origin HEAD:main announce_discord: diff --git a/.macroscope/check-run-agents/ui-consistency.md b/.macroscope/check-run-agents/ui-consistency.md index 047dafbdd4be1..905823d1d1a9e 100644 --- a/.macroscope/check-run-agents/ui-consistency.md +++ b/.macroscope/check-run-agents/ui-consistency.md @@ -44,7 +44,7 @@ The reference for how this codebase wants UI built is the composer banner system - `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 `