Inline rename shows the old name after a successful save
The save succeeds and the toast confirms the new name. The row is wrong only because a page refresh reached the server before the save did.
team-portalFound 2026-10-19Full report in Replay QA
What the recording showed
save() fires the PATCH without awaiting it, then calls router.refresh(). The refetch started 3ms after the PATCH and came back first; the PATCH took 1025ms, so the refreshed row still had the old name.
Actual
After clicking Save, the inline editor closes and the toast "Renamed to “Website Refresh Round Trip”" appears, but the row's name link/text continues to show the old name "Website Refresh QA Renamed" — the row is never updated with the committed name.
Expected
After clicking Save in the inline rename form, the renamed project's row in the projects table should be refreshed to display the newly saved name "Website Refresh Round Trip" (matching the PATCH response body and the confirmation toast).
Session replay
Chronology of events

0:06.4On the Projects dashboard, the inline rename editor for the first row is open with the input filled with the new name "Website Refresh Round Trip" and Save/Cancel buttons visible; the user clicks the row's Save control to commit the rename.
2 evidence probes (UserInteractions, InspectElement)
UserInteractions {"mode":"interactions","type":"click"}
→ # User Interactions
**Filter:** click only
**Showing 1–3 of 3 interactions**
| # | Time | Type | Detail | Point |
|---|------|------|--------|-------|
| 0 | 43969ms | click | Click at (369, 235) | Point:4 |
| 1 | 95451ms | click | Click at (243, 238) | Point:5 |
| 2 | 109965ms | click | Click at (389, 238) | Point:6 |
## Next Steps
- Use DescribePoint with a point reference to inspect the code at a specific interaction
- Use Evaluate with a point reference for ad-hoc value inspection at that moment
- Use `type: 'click'` or `type: 'keypress'` to filter by interaction type
## Playwright-triggered interactions (driver-dispatched input)
The rows and counts above enumerate DOM-recorded input events only. Input a test driver dispatched
programmatically (a Playwright `keyboard.press`, `fill`, driver `click`, ...) can be absent from them
entirely; these are the driver-dispatched input steps of this recording's Playwright run, distilled
from `PlaywrightSteps`. A completed row is affirmative evidence the input WAS dispatched; a failed row
is one attempt, never evidence that none was dispatched.
**Playwright key presses:** 3 — 3 completed, 0 failed
**Playwright pointer input:** 5 — 5 completed, 0 failed
| Step | Time | Method | Params | Result |
| --- | --- | --- | --- | --- |
| 199 | 43003ms | mouse.move | {"x":369.0625,"y":235.5,"steps":24} | completed |
| 206 | 43951ms | locator.click | | completed |
| 268 | 77076ms | keyboard.press | {"key":"Control+A"} | completed |
| 269 …InspectElement {"point":"Point:3","element":"table.list tbody tr td form button.btn-primary"}
→ Element:1 is `<button type="submit" class="btn btn-primary"></button>`.
No elements are obstructing the center point of this element.
Element:1 has 61.921875px width and 32px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 7px right 14px bottom 7px left 14px.
Distances to its parent element:
- Top overflows parent by 0.25px
- Right overflows parent by 158.98px
- Bottom overflows parent by 0.25px
- Left overflows parent by 226.00px
### Parent Element:2
Element:2 is `<form class="name-cell"></form>`.
Element:2 has 446.90625px width and 32.5px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 0px right 0px bottom 0px left 0px.
Distances to its parent element:
- Top overflows parent by 12.50px
- Right overflows parent by 12.00px
- Bottom overflows parent by 12.50px
- Left overflows parent by 12.00px
### Parent Element:3
Element:3 is `<td></td>`.
Element:3 has 470.90625px width and 57.5px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 12px right 12px bottom 12px left 12px.
Distances to its parent element:
- Top is aligned with parent boundary
- Right overflows parent by 567.09px
- Bottom is aligned with parent boundary
- Left is aligned with parent boundary
### Parent Element:4
Element:4 is `<tr></tr>`.
Element:4 has 1038px width and 57.5px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 0px right 0px bottom 0px left 0px.
Distances to its parent element:
- Top is aligned with parent bou…
0:06.5After Save is clicked, the app shows the toast "Renamed to 'Website Refresh Round Trip'" but the first row's Name column still displays the old name "Website Refresh QA Renamed" — the row was not refreshed with the committed name.
Steps to reproduce
Verify the starting page
Click the ✎ rename affordance on any one project row in the projects table to open that row's inline rename editor
Clear the inline 'Project name' field in the renaming row
Type the project's new name into the inline 'Project name' field
Click the row's Save control to commit the rename
Root cause
In components/ProjectsTable.tsx, ProjectName.save() fires the PATCH fetch without awaiting it, then immediately calls router.refresh() and onRenamed(name) on the same tick. Because router.refresh() triggers a concurrent RSC refetch (GET /projects?_rsc=1qm1g initiated at 109972ms, just 3ms after the PATCH started at 109969ms) while the PATCH is still in flight (it completes 1025ms later at 110992ms), the refetched payload still contains the pre-rename project name, so the table row continues rendering "Website Refresh QA Renamed". The toast "Renamed to “Website Refresh Round Trip”" is shown optimistically via onRenamed(name) using the draft name, masking that the row was never updated with the committed name.
5 evidence probes (ReadSource, NetworkRequest, Logpoint, Evaluate)
ReadSource {"line":19,"path":"webpack-internal:///Users/blamy/Documents/qa-bug-bench/team-portal/components/ProjectsTable.tsx"}
→ hits | L | code
------|------|------
104 | 9 | function ProjectName({ project, onRenamed }: { project: Project; onRenamed: (name: string) => void }) {
104 | 10 | const router = useRouter();
104 | 11 | const [editing, setEditing] = useState(false);
104 | 12 | const [draft, setDraft] = useState(project.name);
| 13 |
| 14 | function startEditing() {
1 | 15 | setDraft(project.name);
1 | 16 | setEditing(true);
| 17 | }
| 18 |
| *19 | function save() {
1 | 20 | const name = draft.trim();
1 | 21 | setEditing(false);
0 | 22 | if (!name || name === project.name) /*<HIT>*/return;
| 23 |
1 | 24 | fetch(`/api/projects/${project.id}`, {
| 25 | method: "PATCH",
| 26 | headers: { "Content-Type": "application/json" },
1 | 27 | body: JSON.stringify({ name }),
| 28 | });
1 | 29 | router.refresh();
1 | 30 | onRenamed(name);
| 31 | }
| 32 |
104 | 33 | if (editing) {
| 34 | return (
56 | 35 | <form
| 36 | className="name-cell"
| 37 | onSubmit={(e) => {
1 | 38 | e.preventDefault();
1 | 39 | save();
| 40 | }}
| 41 | >
56 | 42 | <input
| 43 | autoFocus
| 44 | aria-label="Project name"
| 45 | …NetworkRequest {"mode":"detail","requestId":"748.71"}
→ A PATCH network request with ID 748.71 was made to http://localhost:3201/api/projects/p-101:
<request_summary>
The response had status 200 (OK).
</request_summary>
Initiated at Point:1 @ 109969.00ms
Events:
request @ 109969ms
request-body @ 109969ms
response @ 110992ms
response-body @ 110993ms
request-done @ 110994ms
Request headers:
<requestHeaders>
sec-ch-ua:
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/108.0.5359.0 Safari/537.36
sec-ch-ua-platform:
</requestHeaders>
Response headers:
<responseHeaders>
date: Mon, 19 Oct 2026 21:16:11 GMT
connection: keep-alive
keep-alive: timeout=5
vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch
transfer-encoding: chunked
content-type: application/json
</responseHeaders>
The request body was
:
<requestBody>
{"name":"Website Refresh Round Trip"}
</requestBody>
The response body was
:
<responseBody>
{"id":"p-101","name":"Website Refresh Round Trip","description":"Overview rewritten by the edit-persistence smoke test.","owner":"Priya Raman","status":"on-hold","dueDate":"2027-03-15","progress":62,"updatedAt":"2026-10-19T21:16:11.951Z"}
</responseBody>
_Previous: `requestId: "748.70"` | Next: `requestId: "748.72"`_NetworkRequest {"mode":"requests","urlFilter":"/projects"}
→ # Network Requests
**Filters:** URL contains "/projects"
**Showing 1–6 of 6 requests**
_All URLs relative to http://localhost:3201_
| # | ID | Time | Method | URL | Status | Duration | Type |
|---|-----|------|--------|-----|--------|----------|------|
| 0 | 748.1 | 0ms | GET | /projects | 200 | 138ms | (unknown) |
| 1 | 748.10 | 132ms | GET | /_next/static/chunks/app/projects/page.js | 200 | 1449ms | script |
| 2 | 748.26 | 2169ms | GET | /_next/static/chunks/app/projects/page.js | 200 | 1054ms | fetch |
| 3 | 748.69 | 12771ms | GET | /_next/static/chunks/app/projects/page.js | 200 | 367ms | fetch |
| 4 | 748.71 | 109969ms | PATCH | /api/projects/p-101 | 200 | 1025ms | fetch |
| 5 | 748.72 | 109972ms | GET | /projects?_rsc=1qm1g | 200 | 431ms | fetch |
## Next Steps
- Use `mode: 'detail'` with `requestId` (from the ID column) to see full headers, body, and timing for a specific request
- Use `urlFilter`, `startTime`, `endTime` to narrow the listLogpoint {"line":30,"path":"webpack-internal:///Users/blamy/Documents/qa-bug-bench/team-portal/components/ProjectsTable.tsx","expression":"project.name + \" -> \" + name"}
→ Found 1 total hit at webpack-internal:///Users/blamy/Documents/qa-bug-bench/team-portal/components/ProjectsTable.tsx:30
Hit 1 at point Point:3 @ 109923.81ms:
Expression: project.name + " -> " + name
Result: "Website Refresh QA Renamed -> Website Refresh Round Trip"
Evaluate {"point":"Point:7","scope":"global","expression":"(() => { const body = document.body.innerText; return { hasNewName: body.includes('Website Refresh Round Trip'), hasOldName: body.includes('Website Refresh QA Renamed'), firstRow: (document.querySelector('table.list tbody tr td .name-cell a strong') || {}).textContent || null }; })()"}
→ {
"hasNewName": false,
"hasOldName": true,
"firstRow": "Website Refresh QA Renamed"
}Root cause → actual behavior
The save() function in components/ProjectsTable.tsx issues the PATCH fetch (line 24), calls router.refresh() (line 29) and onRenamed(name) (line 30) all on the same tick; the Logpoint at line 30 confirms onRenamed fired with the new name "Website Refresh Round Trip".
2 evidence probes (ReadSource, Logpoint)
ReadSource {"line":19,"path":"webpack-internal:///Users/blamy/Documents/qa-bug-bench/team-portal/components/ProjectsTable.tsx"}
→ hits | L | code
------|------|------
104 | 9 | function ProjectName({ project, onRenamed }: { project: Project; onRenamed: (name: string) => void }) {
104 | 10 | const router = useRouter();
104 | 11 | const [editing, setEditing] = useState(false);
104 | 12 | const [draft, setDraft] = useState(project.name);
| 13 |
| 14 | function startEditing() {
1 | 15 | setDraft(project.name);
1 | 16 | setEditing(true);
| 17 | }
| 18 |
| *19 | function save() {
1 | 20 | const name = draft.trim();
1 | 21 | setEditing(false);
0 | 22 | if (!name || name === project.name) /*<HIT>*/return;
| 23 |
1 | 24 | fetch(`/api/projects/${project.id}`, {
| 25 | method: "PATCH",
| 26 | headers: { "Content-Type": "application/json" },
1 | 27 | body: JSON.stringify({ name }),
| 28 | });
1 | 29 | router.refresh();
1 | 30 | onRenamed(name);
| 31 | }
| 32 |
104 | 33 | if (editing) {
| 34 | return (
56 | 35 | <form
| 36 | className="name-cell"
| 37 | onSubmit={(e) => {
1 | 38 | e.preventDefault();
1 | 39 | save();
| 40 | }}
| 41 | >
56 | 42 | <input
| 43 | autoFocus
| 44 | aria-label="Project name"
| 45 | …Logpoint {"line":30,"path":"webpack-internal:///Users/blamy/Documents/qa-bug-bench/team-portal/components/ProjectsTable.tsx","expression":"project.name + \" -> \" + name"}
→ Found 1 total hit at webpack-internal:///Users/blamy/Documents/qa-bug-bench/team-portal/components/ProjectsTable.tsx:30
Hit 1 at point Point:3 @ 109923.81ms:
Expression: project.name + " -> " + name
Result: "Website Refresh QA Renamed -> Website Refresh Round Trip"
The PATCH /api/projects/p-101 (initiated 109969ms) and the router.refresh RSC refetch GET /projects?_rsc=1qm1g (initiated 109972ms) run concurrently; the PATCH takes 1025ms and only responds at 110992ms, so the earlier-completed RSC refetch (109972→110403ms) returns the pre-rename payload.
2 evidence probes (NetworkRequest)
NetworkRequest {"mode":"requests","urlFilter":"/projects"}
→ # Network Requests
**Filters:** URL contains "/projects"
**Showing 1–6 of 6 requests**
_All URLs relative to http://localhost:3201_
| # | ID | Time | Method | URL | Status | Duration | Type |
|---|-----|------|--------|-----|--------|----------|------|
| 0 | 748.1 | 0ms | GET | /projects | 200 | 138ms | (unknown) |
| 1 | 748.10 | 132ms | GET | /_next/static/chunks/app/projects/page.js | 200 | 1449ms | script |
| 2 | 748.26 | 2169ms | GET | /_next/static/chunks/app/projects/page.js | 200 | 1054ms | fetch |
| 3 | 748.69 | 12771ms | GET | /_next/static/chunks/app/projects/page.js | 200 | 367ms | fetch |
| 4 | 748.71 | 109969ms | PATCH | /api/projects/p-101 | 200 | 1025ms | fetch |
| 5 | 748.72 | 109972ms | GET | /projects?_rsc=1qm1g | 200 | 431ms | fetch |
## Next Steps
- Use `mode: 'detail'` with `requestId` (from the ID column) to see full headers, body, and timing for a specific request
- Use `urlFilter`, `startTime`, `endTime` to narrow the listNetworkRequest {"mode":"detail","requestId":"748.72"}
→ A GET network request with ID 748.72 was made to http://localhost:3201/projects?_rsc=1qm1g:
<request_summary>
The response had status 200 (OK).
</request_summary>
Initiated at Point:9 @ 109972.00ms
Events:
request @ 109972ms
response @ 110348ms
response-body @ 110349ms
request-done @ 110403ms
Request headers:
<requestHeaders>
sec-ch-ua:
next-router-state-tree: %5B%22%22%2C%7B%22children%22%3A%5B%22projects%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2C%22refetch%22%5D
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/108.0.5359.0 Safari/537.36
rsc: 1
sec-ch-ua-platform:
</requestHeaders>
Response headers:
<responseHeaders>
date: Mon, 19 Oct 2026 21:16:11 GMT
content-encoding: gzip
vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encoding
transfer-encoding: chunked
content-type: text/x-component
cache-control: no-store, must-revalidate
connection: keep-alive
keep-alive: timeout=5
</responseHeaders>
The request body was
EMPTY.
The response body was
:
<responseBody>
2:I["(app-pages-browser)/./node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.js",["app-pages-internals","static/chunks/app-pages-internals.js"],"SegmentViewNode"]
4:"$Sreact.fragment"
12:I["(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js",["app-pages-internals","static/chunks/app-page…Consequently the rendered table row still shows the old name "Website Refresh QA Renamed" in the DOM at the end state, even though the PATCH succeeded and returned the new name.
1 evidence probe (Evaluate)
Evaluate {"point":"Point:7","scope":"global","expression":"(() => { const body = document.body.innerText; return { hasNewName: body.includes('Website Refresh Round Trip'), hasOldName: body.includes('Website Refresh QA Renamed'), firstRow: (document.querySelector('table.list tbody tr td .name-cell a strong') || {}).textContent || null }; })()"}
→ {
"hasNewName": false,
"hasOldName": true,
"firstRow": "Website Refresh QA Renamed"
}Is your app hiding bugs like this?
Replay QA explores every build, records what it finds, and writes the root cause.