Skip to content

feat(desktop,build)!: adopt node-native typescript and add windows arm64 support#62

Merged
tyulyukov merged 21 commits into
mainfrom
marcode/port-upstream-shell-typescript-ci
Apr 23, 2026
Merged

feat(desktop,build)!: adopt node-native typescript and add windows arm64 support#62
tyulyukov merged 21 commits into
mainfrom
marcode/port-upstream-shell-typescript-ci

Conversation

@tyulyukov
Copy link
Copy Markdown
Owner

Summary

  • Adopts Node-native TypeScript for desktop and server (moving from bundled TS)
  • Adds explicit .ts extensions to imports across TypeScript/MarCode-exclusive files
  • Updates Electron preload and main bundle outputs to .cjs format
  • Adds ServerListeningDetector and waitForBackendStartupReady for robust backend readiness coordination
  • Implements Wayland window reveal via bindFirstRevealTrigger with did-finish-load fallback
  • Adds Windows ARM64 build target and cross-arch updater manifest merging
  • Updates DevContainer to Node 24.13.1, Bun 1.3.11, Python 3.10
  • Aligns subscription eviction and regression tests with upstream behavior

Testing

  • Desktop backend readiness: listening signal race vs. HTTP probe, custom predicates, timeouts, Wayland scenarios
  • Server listening detector: immediate readiness, chunked log handling, failure modes
  • CI preload verification: validates .cjs output contains expected exports
  • Release manifest merging: per-arch manifests merge into canonical per-channel outputs
  • All TypeScript test suites pass post-recompilation
  • DevContainer feature install order verified (git → bun)
  • Not run: end-to-end Windows ARM64 release pipeline (blocked on self-hosted runner)

juliusmarminge and others added 17 commits April 23, 2026 01:50
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
…ingdotgg#2262)

Partial cherry-pick of upstream 055897f — only the Wayland window reveal portion.
Skips the OpenCode version bump (OpenCodeProvider files) since MarCode does not
vendor the OpenCode provider.
Cleans up duplicate function declarations and missing MarCode-specific
fields introduced when merging f7fa62a (upstream pingdotgg#1973):

- store.ts / ProjectionSnapshotQuery.ts: remove duplicate function
  declarations left by conflict resolution
- ChatView.tsx: use activeThreadEnvironmentId for
  retainThreadDetailSubscription (MarCode does not have routeKind)
- server.test.ts: import PersistenceSqlError
- test fixtures: add jiraBoard / additionalDirectories / compacting
  fields now required by the shell snapshot schemas
The Node-native TypeScript cherry-pick (8dba2d6) only updated files
that existed upstream. MarCode-exclusive files (Jira integration,
GitLab CLI, RoutingGitHostCli, workspaceEntries) needed the same
treatment: explicit .ts extensions on relative imports and type-only
imports where verbatimModuleSyntax requires it.

Also fixes apps/landing/tsconfig.json to override NodeNext resolution
with Bundler for Next.js module resolution.
Co-authored-by: Julius Marminge <julius0216@outlook.com>
- windowState.integration-guard: accept both ./windowState and ./windowState.ts
  import paths so the regex keeps matching after Node-native TypeScript.
- shell.test.ts: replace stale __T3CODE_ENV_ markers with __MARCODE_ENV_
  to match the shell probe implementation after the rebrand.
- service.ts: restore upstream's 2-minute thread detail idle eviction
  (a divergent 15-minute value was introduced during a conflict merge).
In dev mode, the server's catch-all route at "/" returns a 302 redirect
to the Vite dev URL (staticAndDevRouteLayer in apps/server/src/http.ts).
The HTTP readiness probe's default isReady is response.ok, which is
false for 302, so the probe retried for the full 60s timeout before the
catch handler finally loaded the dev URL — giving a blank window for a
full minute at startup.

Also, dev mode sets stdio to inherit, so child.stdout / child.stderr are
null and the ServerListeningDetector never sees the 'Listening on http://'
line. That leaves the HTTP probe as the only path to readiness.

Override isReady to accept any non-5xx response: if the backend replies
at all (200, 302, 401, 404, ...), it is up and serving.
The cherry-pick of upstream f7fa62a ("Add shell snapshot queries")
introduced a retainThreadDetailSubscription useEffect in ChatView,
gated by routeKind === "server". MarCode's ChatView doesn't have a
routeKind prop, so during conflict resolution the gate was relaxed to
just a null-check on activeThreadEnvironmentId. That breaks new threads:

1. Draft route renders ChatView with the draft threadId before the
   thread exists server-side.
2. ChatView immediately opens subscribeThread for that threadId using
   primaryEnvironmentId as fallback.
3. Server returns "Thread not found" — wsTransport swallows the
   non-transport error and lets the stream die, but entry.unsubscribe
   is now a real function (not NOOP).
4. User submits the draft; server creates the thread; client navigates
   to /env/thread.
5. The canonical route's useEffect calls retainThreadDetailSubscription
   for the same (envId, threadId). attachThreadDetailSubscription sees
   entry.unsubscribe !== NOOP and short-circuits. No snapshot is ever
   requested, so the thread stays message-less.

The canonical route _chat.$environmentId.$threadId.tsx already retains
the detail subscription once the thread is real. ChatView's call was
both redundant for real threads and actively harmful for drafts. Remove
it.

Old threads were unaffected because ChatView only mounts for them after
they already exist server-side, so the first subscribe succeeds.
The sidebar was stickily preserving the previous hasPendingApprovals /
hasPendingUserInput / hasActionableProposedPlan flags across every
thread-upserted shell event. This was defensive code from the era when
the shell listing snapshot always reported these flags as false and
relied on detail events to correct them.

After upstream f7fa62a (shell snapshot queries), the server projection
tracks pending_approval_count / pending_user_input_count /
has_actionable_proposed_plan in SQL and updates them in the same
transaction as the corresponding domain event. Every thread-aggregate
event emits a fresh thread-upserted shell event derived from the updated
projection — so the shell event's flags are the authoritative truth.

Keeping the sticky merge caused a ghost "Pending Approval" indicator to
linger in the sidebar forever on threads whose approval had long since
been resolved.

Update the regression tests to reflect the new authority: the shell
event wins. Detail events can still flip flags to true; shell events
can now flip them back to false.
When a batch of parallel tool calls is permitted via acceptForSession
on the first request, the other pending canUseTool callbacks in the
Claude adapter get unblocked via the updated session permissions
without emitting a per-request approval.resolved activity. The
corresponding projection_pending_approvals rows therefore stay
status='pending' indefinitely, even though the turn has long since
completed. The sidebar then shows a ghost "Pending Approval" badge on
a thread that has nothing left to approve; opening the thread shows
no approval UI because the detail subscription correctly reports the
approvals are not pending.

Fix in the projection pipeline: when a thread.turn-start-requested
event arrives (user has moved on to a new turn), sweep any
status='pending' approvals for that thread and mark them resolved
with decision=null. By the time the user starts a fresh turn, any
approvals that were still pending from a previous turn are
definitively abandoned — the provider will never ask for them again.

Regression test: ProjectionPipeline.test.ts
"resolves orphaned pending approvals from earlier turns when a new
turn starts" verifies three orphaned approval.requested rows get
status=resolved with resolvedAt=turn-start-requested.createdAt.

Note: this is a projection-side safety net. The proper fix for the
underlying bug is in the Claude adapter (when acceptForSession grants
session-scoped permission, emit approval.resolved activities for the
pending requests that the updated permissions implicitly covered).
That is a separate change that touches provider code and is better
filed as a dedicated upstream fix.
@tyulyukov tyulyukov merged commit c916d11 into main Apr 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants