Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/fork-features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1904,7 +1904,7 @@ features:
status: maintained
prs: [46, 166]
invariants:
- Sidebar links and proactive panels reuse the same pull request tab when the linked reference supplies its host through the URL; reopening consolidates existing duplicates without merging different hosts or environments.
- Sidebar links and proactive panels reuse the same pull request tab when the linked reference supplies its host through the URL; reopening consolidates existing duplicates without merging different hosts or environments. URL-derived hosts are resolved before rendering and on saved-panel migration, so clicking the same PR link preserves the component and cache identity.
- The command palette and keybinding expose provider-appropriate pull request navigation only when the current thread has an associated change request, then open it in the in-app panel.
- Pull request result toasts include the created pull request number and open the corresponding in-app panel when available, retaining the external URL as a fallback.
implementation_paths:
Expand Down
47 changes: 47 additions & 0 deletions apps/web/src/rightPanelStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,53 @@ describe("rightPanelStore", () => {
},
);

it("keeps the rendered PR reference stable when its chat link is opened", () => {
const linked = {
projectId: "project-a",
repository: "acme/api",
number: 5,
url: "https://github.com/acme/api/pull/5",
};
const store = useRightPanelStore.getState();
store.openProactive(refA, pullRequestSurface(linked), store.getUserActionRevision(refA));
const before = selectActiveRightPanelSurface(useRightPanelStore.getState().byThreadKey, refA);
expect(before).toMatchObject({ host: "github.com" });
store.openPullRequest(refA, { ...linked, host: "github.com" });
expect(selectActiveRightPanelSurface(useRightPanelStore.getState().byThreadKey, refA)).toEqual(
before,
);
});

it("resolves a saved URL-only PR host without changing the active tab", () => {
const legacy = {
id: "pull-request:project-a:acme%2Fapi:5",
kind: "pull-request",
projectId: "project-a",
repository: "acme/api",
number: 5,
url: "https://github.com/acme/api/pull/5",
};
const migrated = migratePersistedRightPanelState({
byThreadKey: {
"env-1:thread-A": {
isOpen: true,
activeSurfaceId: legacy.id,
surfaces: [completedDiff, legacy],
},
},
});
useRightPanelStore.setState(migrated);
expect(selectActiveRightPanelSurface(migrated.byThreadKey, refA)).toEqual({
...legacy,
host: "github.com",
});
useRightPanelStore.getState().openPullRequest(refA, { ...legacy, host: "github.com" });
expect(selectActiveRightPanelSurface(useRightPanelStore.getState().byThreadKey, refA)).toEqual({
...legacy,
host: "github.com",
});
});

it("consolidates previously duplicated PR tabs when reopened", () => {
const linked = pullRequestSurface({
projectId: "project-a",
Expand Down
26 changes: 20 additions & 6 deletions apps/web/src/rightPanelStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ const RIGHT_PANEL_STORAGE_KEY = "styal:right-panel-state:v2";
// v10 keys pull-request surfaces by reference instead of a singleton tab.
// v11 stops persisting the pull-request list's shared panel, so a restart opens the page fresh.
// v12 adds the device surface.
const RIGHT_PANEL_STORAGE_VERSION = 13;
// v14 resolves URL-only pull request hosts before the panel mounts.
const RIGHT_PANEL_STORAGE_VERSION = 14;

/** A fixed workspace-level ref: each PR surface carries its own real environment. */
export const PULL_REQUESTS_PANEL_REF = scopeThreadRef(
Expand Down Expand Up @@ -256,12 +257,18 @@ export function pullRequestSurface(target: {
number: number;
url?: string;
}): PullRequestSurface {
// Linked references carry the host in their URL. Resolve it before rendering so an explicit
// link to the same PR cannot change the panel's React key or its query/snapshot cache keys.
const host =
target.host?.toLowerCase() ??
(typeof target.url === "string" ? parseChangeRequestUrl(target.url)?.host : undefined);
return {
// Keep historical tab IDs: upsertSurface already matches explicit and URL-derived hosts.
id: pullRequestSurfaceId(target),
kind: "pull-request",
...(target.environmentId === undefined ? {} : { environmentId: target.environmentId }),
projectId: target.projectId,
...(typeof target.host === "string" ? { host: target.host.toLowerCase() } : {}),
...(host === undefined ? {} : { host }),
repository: target.repository,
number: target.number,
...(typeof target.url === "string" ? { url: target.url } : {}),
Expand Down Expand Up @@ -429,10 +436,17 @@ export function migratePersistedRightPanelState(persistedState: unknown): {
const { environmentId, ...rest } = surface;
// Anything else stored under that name is not an environment.
return [
pullRequestSurface({
...rest,
...(typeof environmentId === "string" ? { environmentId } : {}),
}),
{
...pullRequestSurface({
...rest,
...(typeof environmentId === "string" ? { environmentId } : {}),
}),
// Resolving the host must not change the selected tab on reload.
...(typeof surface.id === "string" &&
surface.id.startsWith("pull-request:")
? { id: surface.id }
: {}),
},
];
}
if (surface.kind !== "terminal") return [surface];
Expand Down
Loading