Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
e8806f0
fix: updated UI "add project" icon to match the command pallete icon …
zurielbm Apr 29, 2026
6854d2f
[codex] Fix visited timestamp under clock skew (#2408)
juliusmarminge Apr 30, 2026
babb286
[codex] fix terminal dimension validation (#2411)
juliusmarminge Apr 30, 2026
d830a70
fix(server): key AskUserQuestion answers by question text (#2404)
basmilius Apr 30, 2026
18d7f0c
fix(web): prevent iOS Safari auto-zoom on input focus (#1652)
bergholmm Apr 30, 2026
691096c
fix(git): hide stale merged/closed PRs on the default branch (#1966)
GuilhermeVieiraDev Apr 30, 2026
6a7d84c
fix(web): allow closing diff panel in non-git projects (#2413)
pedrokpp Apr 30, 2026
71f5697
fix(web): hide mobile sidebar after thread selection or creation (#1293)
murataslan1 Apr 30, 2026
444f0e8
fix(web): make thread archive button always visible on mobile (#2423)
jappyjan Apr 30, 2026
b44ec88
Narrow the right sidebar and update task panel and diff panel (#2409)
shivamhwp Apr 30, 2026
047e9ee
fix(web): respect iOS safe areas across mobile chrome & other mobile …
jappyjan Apr 30, 2026
8dea3fe
fix: bump `electron` version to v40.9.3 and add it to our trusted dep…
nmggithub May 1, 2026
e67f4fc
fix(mobile): enable touch scrolling in file picker modal (#2420)
jappyjan May 1, 2026
dc9f2bf
fix: opencode is not on PATH on Windows (#2183)
adammansfield May 1, 2026
fc8056d
docs(divergence): record 2026-05-01 upstream quick-fix cycle
tyulyukov May 1, 2026
d7e12fb
ci(quality): raise timeout-minutes from 10 to 15
tyulyukov May 1, 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
quality:
name: Format, Lint, Typecheck, Test, Browser Test, Build
runs-on: ubuntu-24.04
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
Expand Down
59 changes: 57 additions & 2 deletions UPSTREAM_DIVERGENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,51 @@ Sections are ordered by action:

## Ported in the current cycle

**Cycle:** 2026-04-28 · Baseline before cycle: `7f04a4a11` · Baseline after cycle: `ef574febf`
**Cycle:** 2026-05-01 · Baseline before cycle: `9e2182c25` · Baseline after cycle: `dc9f2bfbd`

### marcode/upstream-quick-fixes — upstream quick-fix sync (2026-05-01)

| Upstream | Subject | New SHA |
| ------------------------------------------------------ | ------------------------------------------------------------------------------ | ----------- |
| [#2176](https://github.com/pingdotgg/t3code/pull/2176) | fix: updated UI "add project" icon to match the command palette icon | `e8806f001` |
| [#2408](https://github.com/pingdotgg/t3code/pull/2408) | \[codex\] Fix visited timestamp under clock skew | `6854d2fce` |
| [#2411](https://github.com/pingdotgg/t3code/pull/2411) | \[codex\] fix terminal dimension validation | `babb286ec` |
| [#2404](https://github.com/pingdotgg/t3code/pull/2404) | fix(server): key AskUserQuestion answers by question text | `d830a702b` |
| [#1652](https://github.com/pingdotgg/t3code/pull/1652) | fix(web): prevent iOS Safari auto-zoom on input focus | `18d7f0c67` |
| [#1966](https://github.com/pingdotgg/t3code/pull/1966) | fix(git): hide stale merged/closed PRs on the default branch | `691096c7f` |
| [#2413](https://github.com/pingdotgg/t3code/pull/2413) | fix(web): allow closing diff panel in non-git projects | `6a7d84c40` |
| [#1293](https://github.com/pingdotgg/t3code/pull/1293) | fix(web): hide mobile sidebar after thread selection or creation | `71f5697f2` |
| [#2423](https://github.com/pingdotgg/t3code/pull/2423) | fix(web): make thread archive button always visible on mobile | `444f0e85d` |
| [#2409](https://github.com/pingdotgg/t3code/pull/2409) | Narrow the right sidebar and update task panel and diff panel | `b44ec8829` |
| [#2392](https://github.com/pingdotgg/t3code/pull/2392) | fix(web): respect iOS safe areas across mobile chrome & other mobile fixes | `047e9eed9` |
| [#2427](https://github.com/pingdotgg/t3code/pull/2427) | fix: bump `electron` version to v40.9.3 and add it to our trusted dependencies | `8dea3fe31` |
| [#2420](https://github.com/pingdotgg/t3code/pull/2420) | fix(mobile): enable touch scrolling in file picker modal | `e67f4fc16` |
| [#2183](https://github.com/pingdotgg/t3code/pull/2183) | fix: opencode is not on PATH on Windows | `dc9f2bfbd` |

**Conflict resolutions applied:**

- `ChatView.tsx` (#2408) — kept MarCode's `markThreadVisited(serverThread.id, ...)` call shape because MarCode keys `threadLastVisitedAtById` by raw `serverThread.id` (line 770), not upstream's `scopedThreadKey(scopeThreadRef(...))`. Adopted upstream's second arg (`activeLatestTurn.completedAt`) to fix the clock-skew issue.
- `ComposerPromptEditor.tsx` + `Sidebar.tsx` (#1652) — MarCode already had a 16px iOS-zoom fix from `207e3ed60` (`text-[16px] sm:text-[14px]`); took upstream's `text-base sm:text-[14px]` formulation and `wrap-break-word` migration (Tailwind v4 idiom replacing `break-words`) for canonical phrasing.
- `GitManager.ts` (#1966) — MarCode HEAD already had the `isDefaultBranch && state !== "open"` guard (independently added in `207e3ed60`) but inside MarCode's GitLab-aware `Effect.all` parallel block. Only added upstream's clarifying comment. Test file's `marcode-git-manager-` tempdir prefix was kept (vs upstream's `t3code-`).
- `Sidebar.tsx` + `SettingsSidebarNav.tsx` + `routes/settings.tsx` (#2423) — kept MarCode's `md:` breakpoint pattern for the multi-environment indicator (instead of upstream's `max-sm:`-based always-visible-on-mobile pattern; functionally equivalent), but adopted upstream's `text-muted-foreground/60` contrast tweak. SettingsSidebarNav imports merged: kept MarCode's `PaletteIcon` import (FEATURES.md "Theme System: Appearance settings tab"), added upstream's `useCallback` + `useCanGoBack`.
- `SettingsPanels.tsx` (#2409) — kept MarCode's `descriptors`-based `changedSettingLabels` pattern. Upstream's "Auto-open task panel" SettingsRow was dropped because MarCode removed the `autoOpenPlanSidebar` row from the UI entirely (the schema field still exists for backwards compat but is no longer user-configurable).
- `ChatHeader.tsx` (#2392) — combined MarCode's relative-timestamp imports (`formatRelativeTimeLabel`, `useSyncedRelativeTimeTick`) with upstream's `usePrimaryEnvironmentId` import; combined hook calls in the function body so both sets of state are derived. MarCode HEAD's existing `isRemoteEnvironment` reference (line 158) now resolves correctly.
- `index.css` (#2392) — both MarCode's reduce-motion overrides and upstream's `@utility pt-safe / pb-safe / pl-safe / pr-safe` blocks coexist (independent additions, no overlap).
- `BranchToolbar.tsx` (#2392) — kept MarCode's `@marcode/client-runtime` / `@marcode/contracts` rebrand imports; added upstream's new `lucide-react` icon imports (`ChevronDownIcon`, `CloudIcon`, `FolderGit2Icon`, `FolderGitIcon`, `FolderIcon`, `MonitorIcon`).
- `BranchToolbarBranchSelector.tsx` (#2392) — preserved MarCode's `GitBranchIcon`; took upstream's `min-w-0` + `shrink-0` polish.
- `ChatView.tsx` (#2392) — MarCode's inline composer structure differs substantially from upstream's `ChatComposer`-extracted version. Resolved to keep MarCode's structure entirely; manually backported the safe-area-inset paddings to MarCode's header outer wrapper (web-only, electron path unchanged so traffic-light logic survives) and to MarCode's input-bar wrapper. The `isGitRepo`-conditional `BranchToolbar` placement was kept outside the input-bar `<div>` (where MarCode renders it) instead of upstream's nested-inside placement.
- `routes/_chat.$environmentId.$threadId.tsx` + `routes/_chat.draft.$draftId.tsx` (#2392) — adopted upstream's `h-svh min-h-0 ... md:h-dvh` height pattern (better mobile chrome handling). Kept MarCode's two-arg `<ChatView threadId={...} environmentId={...}>` call sites and intentionally did not adopt upstream's `routeKind` / `onDiffPanelOpen` / `draftId` props (per [MEMORY.md] notes about MarCode lacking the `routeKind` prop, and `retainThreadDetailSubscription` ownership).
- `package.json` (#2427) — kept MarCode HEAD's existing `["electron", "node-pty"]` order (already present from a previous bump); upstream's identical entry just rearranged.

**Skipped:**

- [#2277](https://github.com/pingdotgg/t3code/pull/2277) `feat: Multi-Provider support` — explicit user instruction; out of scope for this quick-fix cycle.

---

## Previous cycle: 2026-04-28

**Baseline before cycle:** `7f04a4a11` · **Baseline after cycle:** `ef574febf`

### PR #72 — upstream sync (2026-04-28)

Expand Down Expand Up @@ -167,6 +211,8 @@ These upstream PRs are **behaviorally present** in MarCode via non-identical pat
| [#2153](https://github.com/pingdotgg/t3code/pull/2153) | Redesign model picker with favorites and search | `41ddce8f0 feat(model-picker): port upstream sexy redesign with favorites and search` — explicit port. |
| [#2192](https://github.com/pingdotgg/t3code/pull/2192) | fix(server): prevent probeClaudeCapabilities from wasting API requests | Already present: `waitForAbortSignal` + `SDKUserMessage` never-yielding prompt in `ClaudeProvider.ts:485,514`. Cherry-pick diff is empty against our HEAD. |
| [#2255](https://github.com/pingdotgg/t3code/pull/2255) | fix(server): restore CODEX_HOME tilde expansion for Codex launches | `expandHomePath` already wired on `CodexProvider.ts:226` and `CodexSessionRuntime.ts:688` via [#2210](https://github.com/pingdotgg/t3code/pull/2210) + follow-ups (`63ea04e29`, `42afbb226`). |
| [#2419](https://github.com/pingdotgg/t3code/pull/2419) | fix(web): make new thread button always visible on mobile | MarCode independently solved this in `207e3ed60 feat(composer): refactor mention node ...` (Apr 14) using a `md:` breakpoint inversion (`pointer-events-auto absolute ... md:pointer-events-none md:opacity-0 md:group-hover:...:opacity-100`) — semantically equivalent to upstream's `max-sm:` pattern but inverted. Cycle 2026-05-01 cherry-pick was skipped via `git cherry-pick --skip`. |
| Direct | Stop OpenCode refresh from leaking serve processes (`35822884d`) | `1feaf81b5 / 71199bbb7` — already ported earlier (detected by `git cherry`'s patch-id match, did not appear in 2026-05-01 missing list). |

**Verification strategy for re-checking in a later cycle:** grep for the symbol the upstream PR adds. If it's already in MarCode, confirm; do not cherry-pick.

Expand Down Expand Up @@ -202,11 +248,20 @@ MarCode ships semver alphas (`1.0.0-alpha.*`), not nightly builds. Adopting nigh
- `2d87574e` `chore(release): prepare v0.0.20` — bumps to upstream's 0.0.x version scheme, collides with MarCode's `1.0.0-alpha.*`.
- `ada410bc` `chore(release): prepare v0.0.21` — same.

### Upstream-only docs / rosters (skipped)

- [#2154](https://github.com/pingdotgg/t3code/pull/2154) `Add OpenCode to README` — upstream's README is divergent from MarCode's marketing-oriented README; not worth re-adding the line.
- [#2425](https://github.com/pingdotgg/t3code/pull/2425) `Add new GitHub users to VOUCHED.td` — upstream's volunteer roster file; doesn't apply to the fork.

### Multi-Provider support (deferred, not skipped permanently)

- [#2277](https://github.com/pingdotgg/t3code/pull/2277) `feat: Multi-Provider support` — deferred from the 2026-05-01 cycle on user instruction (too large for a quick-fix sync). Revisit in a dedicated cycle when MarCode is ready to integrate the multi-provider model.

---

## Pending real work

_None as of 2026-04-28._ The two upstream commits in this cycle (#2364 release-smoke node fix + #2372 stale WS lifecycle gate) landed via PR #72. Re-run the `git cherry origin/main upstream/main` workflow at the top of this doc when starting a new cycle to populate this section.
_None as of 2026-05-01._ All actionable upstream commits in the new range (`9e2182c25..1eb6fcea8`, 18 commits flagged by `git cherry`) landed via the `marcode/upstream-quick-fixes` branch except: `#2277` (deferred — see "Multi-Provider support" above), `#2154` and `#2425` (upstream-only docs/rosters), and `#2419` (already-equivalent under MarCode's `md:` inversion pattern). Re-run the `git cherry origin/main upstream/main` workflow at the top of this doc when starting the next cycle.

---

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron": "40.9.3",
"electron-updater": "^6.6.2"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/git/Layers/GitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
}).pipe(
Effect.map((latest) => {
if (!latest) return null;
// On the default branch, only surface open PRs.
// Merged/closed matches are usually reverse-merge history, not the thread's PR context.
if (details.isDefaultBranch && latest.state !== "open") return null;
return toStatusPr(latest);
}),
Expand Down
31 changes: 31 additions & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3468,6 +3468,9 @@ describe("ClaudeAdapterLive", () => {
assert.equal(typeof requestId, "string");
assert.equal(requestedEvent.value.payload.questions.length, 1);
assert.equal(requestedEvent.value.payload.questions[0]?.question, "Which framework?");
// Regression for #2388: `id` must equal the full question text so the
// UI's draft-answer key matches what the SDK looks up downstream.
assert.equal(requestedEvent.value.payload.questions[0]?.id, "Which framework?");
assert.deepEqual(requestedEvent.value.providerRefs, {
providerItemId: ProviderItemId.make("tool-ask-1"),
});
Expand Down Expand Up @@ -3502,6 +3505,34 @@ describe("ClaudeAdapterLive", () => {
assert.deepEqual(updatedInput.answers, { "Which framework?": "React" });
// Original questions should be passed through.
assert.deepEqual(updatedInput.questions, askInput.questions);

// Compatibility check for #2388: the answers shape we hand to the SDK
// must produce a non-empty rendered tool_result on BOTH SDK iteration
// patterns we have seen, so we don't regress the issue and we don't
// break users still on the older Claude CLI.
const sdkAnswers = updatedInput.answers as Record<string, unknown>;
const sdkQuestions = updatedInput.questions as ReadonlyArray<{
readonly question: string;
}>;

// Claude CLI 2.1.119 — key-agnostic Object.entries iteration. Any key
// works here, but it must at least round-trip into a non-empty string.
const v119Rendered = Object.entries(sdkAnswers)
.map(([key, value]) => `"${key}"="${String(value)}"`)
.join(", ");
assert.equal(v119Rendered, '"Which framework?"="React"');

// Claude CLI 2.1.121 — lookup by full question text. This is the path
// that regressed in #2388 when the answers were keyed by `header`.
const v121Rendered = sdkQuestions
.map(({ question }) => {
const answer = sdkAnswers[question];
return answer === undefined ? null : `"${question}"="${String(answer)}"`;
})
.filter((entry): entry is string => entry !== null)
.join(", ");
assert.notEqual(v121Rendered, "", "Expected non-empty SDK 2.1.121 tool_result (#2388)");
assert.equal(v121Rendered, '"Which framework?"="React"');
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
Expand Down
6 changes: 5 additions & 1 deletion apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2900,10 +2900,14 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
const requestId = ApprovalRequestId.make(yield* Random.nextUUIDv4);

// Parse questions from the SDK's AskUserQuestion input.
// `id` MUST equal the full question text — Claude SDK >= 2.1.121 looks
// up answers by question text in `mapToolResultToToolResultBlockParam`,
// so the key the UI uses to keep its draft answer must match the SDK's
// expected lookup key. See https://github.com/pingdotgg/t3code/issues/2388
const rawQuestions = Array.isArray(toolInput.questions) ? toolInput.questions : [];
const questions: Array<UserInputQuestion> = rawQuestions.map(
(q: Record<string, unknown>, idx: number) => ({
id: typeof q.header === "string" ? q.header : `q-${idx}`,
id: typeof q.question === "string" && q.question.length > 0 ? q.question : `q-${idx}`,
header: typeof q.header === "string" ? q.header : `Question ${idx + 1}`,
question: typeof q.question === "string" ? q.question : "",
options: Array.isArray(q.options)
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/opencodeRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
.spawn(
ChildProcess.make(input.binaryPath, args, {
detached: process.platform !== "win32",
shell: process.platform === "win32",
env: {
...process.env,
OPENCODE_CONFIG_CONTENT: JSON.stringify({}),
Expand Down
5 changes: 4 additions & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content"
/>
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#161616" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#161616" />
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/AppSidebarLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) {
}, [navigate]);

return (
<SidebarProvider defaultOpen>
<SidebarProvider className="h-dvh! min-h-0!" defaultOpen>
<Sidebar
side="left"
collapsible="offcanvas"
Expand Down
Loading
Loading