* fix(github): title GitHub issue links that were saved without one
A workspace linked to a GitHub issue from the CLI (`--issue N`), a phone, the paired web
client or a typed number kept only the number, so search could match its issue title only
while a client happened to hold that issue. The host that owns the workspace now reads each
newly linked untitled issue once, after the write returns, the way its card would, and saves
the title on the link. It keeps the current review selection and never resurrects a removed link.
* fix(github): bound and publish issue-title fills like other metadata writes
Skip writes that leave links untouched before normalizing, check a queued link is still wanted
before reading it, publish filled titles once per repo per 2 s window, refresh the resolved rows
remote clients read, never wake a stopped WSL distro for a background read, keep a selected
review's PR suppression, and keep a failing write observer from failing a saved write.
* fix(github): keep link-title fills off unrelated writes and out of per-title refreshes
Ignore linkedWorkItem in the unchanged-links check (it is rebuilt on every write and always
titled), publish filled titles at once on a quiet host and at most once per repo per 30 s
during a burst on a timer, and only lift a PR suppression when the selected PR changes, so
restating the current PR (as a title fill does) keeps a hidden branch PR hidden.
* perf(github): publish filled link titles as addressed workspace metadata
Instead of a whole-repo worktreesChanged refresh per burst window, the host publishes one
addressed workspaceMetadata set record {hostId, epoch, seq, id, row} for the workspace whose
link was titled: to its desktop window over IPC and to paired clients as a runtime client event.
Clients patch that one row (Cmd-J reads it) and drop replayed records; older clients ignore the
new event and see the title on their next listing.
* fix(github): agreed workspaceMetadata record shape, per-id seqs and late-listing refill
Records name their kind and domain, clients keep the last applied seq per workspace for each
producer epoch (dropped when the epoch changes), and a listing that read metadata before the
title write cannot drop the title: recorded titles refill rows that still hold the same untitled
link for 60 s, never restoring a removed link. The IPC channel is marked as interim transport.
* fix(github): title-only, host-addressed application of workspaceMetadata records
Clients fill the record's issue titles into the named row's still-untitled links and never take
its links over the row's current ones, so a record that arrives after an unlink cannot restore
the issue. A paired host's records reach the row of their own execution host (an SSH twin is a
different row), and go through the bridge's connection-generation check like other host events.
* test(github): type the metadata-record test helper as AppState
* fix(github): keep recorded titles until expiry, refill only changed repos, retire with the pairing
A record that beats the listing adding its link keeps its titles for the full window; the refill
runs only for repos whose listing changed, indexes that repo's rows once and skips rows already
checked; per-workspace seqs live in the expiring entries; a re-pair or reconnect of the producing
environment retires its titles.
* fix(github): read the listing through the workspace record selector
* fix(github): keep link titles until their producer changes; fall back to a listing on eviction
Recorded issue titles no longer expire on a timer (a slow listing could outlast it): they live until
the producer's epoch, pairing or connection changes, refilling only present untitled links. When
more than the cap are in flight the oldest is evicted and its repo is listed afresh, which carries
the saved title. The refill runs in updater form so it never merges a stale store snapshot.
* fix(github): sequenced listings replace the retained-title refill
Per the re-plan ruling: the store keeps one workspaceMetadata cursor per process; a title record takes
the next seq in the same turn as its write, and each listing records the cursor when it reads metadata.
Clients note each record's seq per producer and repo; a listing at or past it ends the obligation, an
older one keeps titles the rows already show and lists the repo afresh (a new invocation, never joining
the stale one), a record whose row or link isn't listed yet asks for a fresh listing, and a seq gap
lists every repo of that producer. The retained-title table, its watcher, TTL and cap are gone.
* test(github): stamp capture, gap, restart and recovery coverage for workspace metadata listings
Also retire an environment's earlier coverage bookkeeping when it is re-paired or reconnects.
* fix(space): keep issue-title coverage per repo and host until a newer listing lands
Coverage now keeps each projection's newest record seq after its covering listing settles, so an older listing that finishes later still keeps shown titles. Projections are keyed by repo and host, paired producers keep their stream cursor, retired pairings and unlisted projections are dropped, every listing served by this host carries its metadata stamp, and a record with any unlisted titled link asks for a listing.
* fix(space): re-list after any stale listing and drop retired metadata producers
A stale listing now always asks for one fresh listing, so a link it dropped comes back and a failed scan can no longer end the wait. Removed environments drop their producer state, the producer's repo list is built only on a gap or a first-seen projection, and recovery listings skip the lineage refresh.
* perf(space): filter current rows only for listings that keep titles
* fix(space): forget removed repos on a metadata gap; keep titles for another producer run's listing
* fix(palette): search a linked issue by its saved title before its card renders
The issue's search entry took its title only from the issue cache, which fills when the card renders, and it shadowed the saved link's titled entry under the same id. It now falls back to the link's saved title.
* perf(palette): read a row's links once per palette document
* fix(palette): give each saved link its own search unit
Saved-link evidence was meant to carry an `attachment:` id but the override set an unused field, so a link shared the `issue:N` unit id with the legacy issue field and with same-number links from other repos, and only the first (often untitled) survived. The id now applies to the unit and its fields, so every saved link's title is searchable. Replaces the cache-title fallback.
* fix(space): keep the title queue draining past a failed link; encode environment ids in coverage keys
---------
The strict target check added in #38514 rejected a preview answer whose type
differed from the request, so every /issues/N link to a pull request (all bare
#N references in chat) showed "Preview unavailable.". Previews now match on
repository and number and accept either type; other callers stay strict.
Also log the failure class of a swallowed owner/repo lookup error.
* feat(runtime): tell phones when the desktop refreshes a PR
* fix(runtime): name every checkout a coalesced PR read stood for
* fix(runtime): don't count a read that couldn't load checks as a checks change
* perf(github): answer phone PR lookups from the desktop's last result
* fix(github): order last PR outcomes by lookup start sequence
* fix(github): keep reused phone PR outcomes out of the refresh observers
* fix(github): reuse a fallback-found PR only for the same fallback number
* perf(github): use the desktop's PR fallback rule on the phone and forward refreshes
* fix(github): keep a merged fallback PR while the phone has no head
* fix(github): stop PR pages from hanging when the rate-limit cache expires
Slot holders await assertRateLimitBudget -> getRateLimit while holding a
gh semaphore slot, and the /rate_limit probe queued for a slot behind
them. With all four slots held by budget waiters nothing ever released.
The probe is quota-exempt and deduped, so it no longer takes a slot.
* fix(github): bound rate-limit probe concurrency without the gh semaphore
Forced getRateLimit calls now join an in-flight probe, and all /rate_limit
probes share a small probe-only limiter that acquires nothing else.
Fold the deadlock regression test into rate-limit.test.ts as a direct await.
* fix(github): make forced rate-limit probes read quota after the call
Forced callers (403 reset refinement) share one follow-up probe queued
behind the running one instead of joining a probe that may predate the
403, so a stale positive budget cannot clear a fresh breaker block.
* fix(github): hand forced rate-limit follow-ups off synchronously
A pending forced follow-up could outlive its probe's settlement, so a
later forced caller joined a probe that predated it. Settlement now
detaches the shared deferred follow-up and starts its probe in one
synchronous step. Run rate-limit.test.ts on the Node runtime, where the
V8 scheduling that opens the race is reproducible.
* refactor: import journal queue state from defining owners
* refactor(shared): organize execution and automation owners
* fix: retain type imports used by surviving test cases
* refactor: finish work item owners and refresh validation metadata
* test: infer reconnect status fixture type
* test(claude): resolve source files beside same-named directories
* test(runtime): group repository creation mock registrations
* chore(lint): bump oxlint to 1.91, add perf lint rules and perf audits
* fix(lint): await promises returned inside try/catch/finally for return-await
Prepare typescript/return-await with error-handling-correctness-only: await
returns whose catch or finally was meant to cover them, and suppress the eight
sites where the catch must not re-handle the returned promise.
* chore(lint): enable return-await (error-handling-correctness-only); exempt secondary-scan directives in the changed gate
* refactor(mobile): move file preview discard modal into its own module
Keeps MobileFilePreviewScreen under the 400-line limit after merging main.
---------
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
RpcDispatcher now requires its method list, so these suites can no longer consume
the default manifest. Suites that still reach the registry through the RPC server
keep the guard.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Shows CI result lines in the chat that opened or pushed to a pull request. They're hidden in the terminal overlay and on the phone until the phone app gets the setting.
* fix(tasks): confirm a lost Tasks comment by author on GitLab and GitHub REST reads
Only GitHub GraphQL's viewerDidAuthor marked the viewer's comments, so on GitLab,
the GitHub REST fallbacks (including issues with more than 100 comments) and the
gh view fallbacks the phone kept an unconfirmed draft even after the comment
posted. Those reads now match the login of the account that made them, and an
explicit PR refresh reads comments uncached.
* fix(mobile): settle an unconfirmed comment only on one posted after the send
A refresh of an issue with more than 100 comments reads comments the first
load never showed, so an older identical comment by the same account looked
new and could clear the draft.
* refactor(gitlab): share the glab viewer read and skip it on Checks polling
* test(mobile): pin the clock in the unconfirmed comment test
An unconfirmed send settled as posted when the refreshed thread had any new
comment with the same text, so another person's identical comment cleared the
draft. The host now passes GitHub's viewerDidAuthor, and the phone settles only
on a comment the viewer wrote. Without it (GitLab, REST fallbacks, older hosts)
the draft stays with the check-the-thread notice.
* Upload GitHub images when pasted or dropped
Keep image bytes out of issue and review bodies by uploading from the owning composer and inserting hosted attachment URLs.
Co-authored-by: lydiassssz <60514233+lydiassssz@users.noreply.github.com>
* Preserve GitHub image intake across editor and thread states
* Keep image placeholder boundaries and ordinary HTML paste intact
* Match the native replace overload in the image projection fixture
* Use typed element queries in the image intake fixture
* Narrow textarea elements at the fixture boundary
* Show failed GitHub draft images as filename links
* Keep GitHub draft attachment links current across updates
* Supply the repository address in the attachment preview fixture
---------
Co-authored-by: lydiassssz <60514233+lydiassssz@users.noreply.github.com>
* feat(native-chat): show details on link cards for any repo on a known host
A chat link card for a pull request or issue in a repository other than the
workspace's own showed only its name, because the lookup answered only for the
workspace's configured remotes. Previews now read any repository on a host those
remotes use, with that host's sign-in, as the common pattern does. Other lookups
keep the exact-repository check, and an unknown host still gets no credentials.
* fix(native-chat): open link cards for other repos on the chat repo's host
The hover card still admitted only the chat repo's own links, so the wider
lookup was never reached. Links to any repo on a host the chat repo uses now
open the GitHub card. When an older host or a repo the sign-in can't read
leaves it unanswered, the card falls back to the web preview, or just the
link's name, as such a link showed before, instead of a failure.
* fix(native-chat): keep another repo's web preview until its details arrive
A link card for another repo showed the GitHub skeleton first and only fell back
once the lookup failed, so older hosts got two loading stages and a change of
shape. It now shows the web preview it always showed until the details arrive,
and keeps it when none do.
* fix(native-chat): show just the name for another repo's link while its details load
With web previews off, a link card for another repo showed the GitHub skeleton
and collapsed to the name when no details came. It now shows the name from the
start, so nothing collapses when an older host or an unreadable repo answers
with nothing.
* feat(checks): show when each check ran and how long it took
Check rows in the right-sidebar Checks panel now show how long a
finished check took and how long ago it finished (or started, while it
runs), with exact start and finish times on hover. GitHub check runs,
legacy commit statuses and GitLab jobs carry provider run times; rows
without times show nothing.
A GitLab pipeline that ran on a commit the merge request has since moved
past says so. The GitHub rollup read keys its 60s cache by the expected
head, so a push never serves the previous head's checks.
* test(checks): bound the clock work of check time labels
* perf(checks): rebuild check time text only when its data changes
Clock ticks now move only each row's age; exact times, duration and copy
are built once per data or language change. Move the run-time mapping
tests to the mapping layer to keep the client test file within budget.
* fix(checks): annotate the gh pr checks reply instead of casting it
* fix(checks): say when a running check started; key the rollup cache by a query comment
A running row reads 'started 5m ago' so its age isn't read as when it
ran. The expected head now rides in a GraphQL comment, which servers
ignore but gh's cache key includes. Add the new copy to the English
catalog and test behavior rather than formatted copy.
* fix(checks): let check times truncate before names in a narrow sidebar
At the narrowest sidebar the time text never shrank, squeezing check
names to nothing and pushing status labels out of their rows. The time
now sits beside the name, gives up width first, and the name keeps a
minimum width.
* fix(checks): rebuild check time copy when translation catalogs change
* fix(checks): reserve room for a running check's growing start age
* test(checks): keep REST check-run times covered through the schema
* fix(checks): read the checks rollup without gh's disk cache
gh's cache key ignores the PR head, so after a push it answered with the
previous head's checks, and it never evicts expired entries. Orca's own
head-keyed checks cache and read coalescing already bound the requests.
* fix(checks): hide check times in rows too narrow to show them
Below a 17rem row the time text is hidden, so the narrowest sidebar keeps
main's name, status and link layout; wider rows truncate the time before
the name, which keeps a 36px minimum.
* fix(checks): truncate a check's duration before its age
The age answers when a check ran, so in a tight row the duration now
gives up its width first.
* test(checks): type the rollup fixture
* fix(checks): show check times only in rows with room for them
Between a 17rem and 19rem row the times squeezed names to their minimum
and clipped; hold them back until 19rem, which the default 350px sidebar
clears.
* fix(checks): keep a minimum name width only while check times show
Rows too narrow for times, including the narrower folder-workspace parent
PR rows, now lay out exactly as before.
* fix(checks): show a running check's elapsed time instead of its start age
'started 10m ago' reserved so much room that running check names shrank
to a few characters at the default sidebar width; beside a running status
the elapsed time ('10m') says the same thing in a quarter of the space.
* test(checks): use a type import for the mocked module
Forge detection asks GitLab, GitHub, GitHub Enterprise, Bitbucket, Azure
DevOps and Gitea in turn, and each probe ran its own git remote get-url.
Share one read per remote across a single detection pass; the scope
closes when detection returns, so later reads see current config.
Co-authored-by: m4air <m4air@Mac.localdomain>
Show cancelled PR checks as Cancelled with separate counts on desktop and mobile, preserving genuine failures, pending checks, existing action permissions and older-client fallback. Carry complete provider summaries through caches and presentation; preserve Azure source completeness, folder attention priority and muted cancellation details.
Tracked in #15847 and STA-5102.