Skip to content

improvement(tables): announce locks on open instead of a header chip - #5979

Merged
TheodoreSpeaks merged 8 commits into
stagingfrom
fix/table-lock-followup
Jul 28, 2026
Merged

improvement(tables): announce locks on open instead of a header chip#5979
TheodoreSpeaks merged 8 commits into
stagingfrom
fix/table-lock-followup

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Removed the lock entry from the table header actions — the header is back to just Import CSV / Export CSV
  • A locked table now announces itself once on open with an info toast, carrying a Lock settings action button for admins
  • Non-admins get the same notice without the button; blocked-action toasts are unchanged
  • The breadcrumb dropdown keeps its "Lock settings" item, so admins still have a permanent way into the panel on an unlocked table

The announcement is marked shown as soon as the table resolves, so an admin who just set locks in the panel doesn't immediately get toasted about them.

Type of Change

  • Improvement (UX)

Testing

Tested manually. bun run type-check, bun run lint:check, and the full audit suite (boundaries, api-validation:strict, utils, zustand-v5, react-query, client-boundary, bare-icons, icon-paths, realtime-prune, skills:check, agent-stream-docs:check) all pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Drop the lock entry from the table header actions. A locked table now announces
itself once on open via an info toast carrying the Lock settings action for
admins; the breadcrumb dropdown remains the permanent entry point.
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 28, 2026 3:07am

Request Review

@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Toast navigation clearing runs at render for all app routes; the table lock announcement depends on permission timing and several dismiss edge cases.

Overview
Locked tables no longer show a lock chip in the header (Import/Export only). Opening a table with any locks triggers a one-time info toast describing what is locked; admins get a Lock settings action, non-admins get the same copy without the button. Blocked-action toasts stay warnings.

The table page dismisses that notice when you leave the table, when tableId changes (including embedded swaps), and if admin access is revoked mid-toast so a stale settings button cannot appear. The on-open toast waits for permissions to finish loading before firing.

ToastProvider clears route-scoped toasts during render on pathname change instead of in a useEffect, so mount-time toasts from a newly opened page (e.g. cached table data) are not swept in the same commit before they paint.

Reviewed by Cursor Bugbot for commit b505ef6. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR replaces the table-header lock chip with a one-time lock announcement and tightens toast lifecycle handling.

  • Announces locked tables with an informational toast after permissions resolve.
  • Offers administrators a Lock settings action and dismisses the toast if that access is lost.
  • Clears table-scoped toasts during table or route transitions while preserving explicitly global notifications.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported stale-permission action is dismissed when lock-settings access is lost.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/lock-copy.ts Updates lock-notice documentation to reflect the new on-open informational announcement.
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx Removes the header lock action, adds the one-time announcement, and safely dismisses captured actions when access or table context changes.
packages/emcn/src/components/toast/toast.tsx Moves route-scoped toast filtering into the provider render transition so newly mounted route notifications are not swept.

Reviews (7): Last reviewed commit: "fix(tables): reset the announce latch wh..." | Re-trigger Greptile

Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx Outdated
Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx Outdated
The lock announcement fires once per table, so a canAdmin that is still false
because permissions have not resolved permanently drops the toast's Lock
settings action. Arm the one-shot on the permissions decision rather than on
table resolution.
Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx
The toast provider clears route-scoped toasts in a pathname effect that runs
after child effects, so an announcement fired on a warm-cache navigation was
added and removed in the same commit. Opt these toasts out of the sweep and
dismiss them on unmount so they still don't trail the user.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit f3d8588. Configure here.

A toast's action is captured at creation, so a viewer whose admin access is
revoked while the announcement is on screen kept a Lock settings button that
opened nothing. Dismiss the notice when the action is no longer available.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

Addressed the outstanding toast-action lifecycle concern in cad3600.

The observation was correct: a toast's action is captured when the toast is created, so a viewer whose admin access was revoked during the ~8s the announcement is on screen kept a Lock settings button that opened nothing — LockSettingsModal is gated on userPermissions.canAdmin at render (and the route is enforced server-side), so there was never an access impact, but the dead button was real.

Rather than trying to keep a live action in a static toast, the view now drops the notice when the action stops being valid:

useEffect(() => {
  if (canOpenLockSettings || !blockedToastIdRef.current) return
  toast.dismiss(blockedToastIdRef.current)
  blockedToastIdRef.current = null
}, [canOpenLockSettings])

This pairs with the unmount cleanup added in f3d8588 — the view owns its toasts' lifecycle end-to-end, which is what persistAcrossRoutes: true obliges it to do.

One direction deliberately not handled: a viewer who gains admin mid-toast keeps the buttonless notice for its remaining duration. That fails safe (the breadcrumb dropdown is the permanent entry point) and re-creating a toast under the user to add a button would be worse than leaving it.

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx
Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx
The dismiss effect treated "has no access" the same as "just lost access", so
on a warm-cache mount — where the announcement and the dismiss both run in the
mount commit — a non-admin's notice was torn down the moment it was created.
Dismiss on the true-to-false transition only.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx
Switching tables reuses this component rather than remounting it, and the
notice is exempt from the provider's route sweep, so a locked table's
announcement stayed on screen over the next table — where its action would
have opened that table's lock settings instead. Key the cleanup on tableId.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 072f658. Configure here.

…ffects

The provider cleared route-scoped toasts from a pathname effect. Effects run
child-first, so it also swept toasts the newly rendered route had just raised:
any toast added from a child's mount effect — which is what happens whenever
that child's data is already cached — was appended and filtered out in the same
commit, before it painted.

Move the sweep into render, where it runs before children render, so only
toasts predating the navigation are cleared. Timers and heights were already
reconciled by effects keyed off `toasts`, so this stays a pure state update.

Drops the persistAcrossRoutes workaround the table lock notice needed to dodge
the bug; its tableId-keyed cleanup stays, covering embedded swaps that change
the prop without a route change.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

Added the underlying emcn fix in d2d2156, since the table-side workaround was only ever dodging it.

The bug. ToastProvider cleared route-scoped toasts from a [pathname] effect. Effects run child-first, so that effect also swept toasts the newly rendered route had just raised — any toast added from a child's mount effect, which is what happens whenever that child's data is already cached, was appended and filtered out in the same commit, before it painted. Not table-specific: it applied to every consumer in the app.

The fix. Move the sweep into render, where it runs before children render, so only toasts predating the navigation are cleared:

const [sweptPathname, setSweptPathname] = useState(pathname)
if (pathname !== sweptPathname) {
  setSweptPathname(pathname)
  setToasts((prev) =>
    prev.some((t) => !t.persistAcrossRoutes) ? prev.filter((t) => t.persistAcrossRoutes) : prev
  )
}

This is React's "adjusting state during render" pattern, and it stays a pure state update — the previous version cleared timers inline, which would be an illegal side effect during render. It doesn't need to: the timer effect already reconciles orphans against toasts (for (const [id, timer] of timers) if (!toasts.some(...))), and a separate effect prunes stale heights. A timer that fires before reconciliation calls dismissToast on an id that is already gone, which is a no-op.

Behavior delta: the old effect also ran on mount. That was always a no-op — a freshly mounted provider has an empty stack — so nothing is lost.

Knock-on: persistAcrossRoutes: true is gone from the lock notice; it existed solely to escape the sweep, and it was the direct cause of the cross-table leak fixed in 072f658. The tableId-keyed cleanup stays, since it also covers the embedded mothership view swapping the tableId prop without a route change.

Net −9 lines across both files. No tests exist for this component, so this is reasoned, not verified — the paths worth exercising by hand are: raise a toast then navigate away (should clear), navigate into a route that toasts from cached data (should now survive), and the permissions provider's persistAcrossRoutes connection toast (should still survive both).

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx
The tableId cleanup dismissed the notice on departure but left the latch
holding that table's id, so returning before another table announced — a quick
there-and-back, or a second table that never loaded — found the latch matching
and stayed silent. Leaving ends the visit, so clear the latch with it.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b505ef6. Configure here.

@TheodoreSpeaks
TheodoreSpeaks merged commit e5ae445 into staging Jul 28, 2026
20 checks passed
@TheodoreSpeaks
TheodoreSpeaks deleted the fix/table-lock-followup branch July 28, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant