diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 980dcd62a..a5aa66e26 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,6 +47,9 @@ jobs:
- name: Plugin license policy (0196)
run: pnpm check:plugin-licenses
+ - name: Motion vocabulary (0199)
+ run: pnpm check:motion-vocab
+
# The heavy half of the old lint job: build the workspace and typecheck it.
# typecheck genuinely needs `^build` (turbo builds dependencies first), so it
# stays coupled to the build. Runs in parallel with `lint`, `test`, and
diff --git a/apps/web/src/components/DataWorkspaceView.tsx b/apps/web/src/components/DataWorkspaceView.tsx
index 69aa33760..1dfe37518 100644
--- a/apps/web/src/components/DataWorkspaceView.tsx
+++ b/apps/web/src/components/DataWorkspaceView.tsx
@@ -701,7 +701,7 @@ function SocialImportJobsPanel({ jobs }: { jobs: SocialImportJobProgress[] }): J
aria-label={`Import progress for ${job.archiveName}`}
>
-
+
@@ -204,6 +204,6 @@ export function StorageWarningBanner({
-
+
)
}
diff --git a/apps/web/src/components/UndoToast.tsx b/apps/web/src/components/UndoToast.tsx
index 498d7ee87..7aedc178f 100644
--- a/apps/web/src/components/UndoToast.tsx
+++ b/apps/web/src/components/UndoToast.tsx
@@ -7,6 +7,7 @@
* keyboard hint reminds users the action is reversible.
*/
import { useGlobalUndo } from '@xnetjs/react'
+import { Presence } from '@xnetjs/ui'
import {
createContext,
useCallback,
@@ -57,28 +58,40 @@ export function UndoToastProvider({ children }: { children: ReactNode }): JSX.El
await undo()
}, [undo])
+ // Latch the last toast so its text survives the exit animation, when
+ // `toast` has already flipped to null but
is still animating out.
+ // Horizontal centering uses auto-margins (not -translate-x-1/2) so the
+ // slide-up keyframe's translateY animates cleanly without fighting a static
+ // transform.
+ const lastToastRef = useRef<{ id: number; message: string } | null>(null)
+ if (toast) lastToastRef.current = toast
+ const shown = toast ?? lastToastRef.current
+
return (
{children}
- {toast ? (
-
- {toast.message}
-
-
- ⌘Z
-
-
- ) : null}
+
+ {shown ? (
+
+ {shown.message}
+
+
+ ⌘Z
+
+
+ ) : null}
+
)
}
diff --git a/apps/web/src/routes/social-import.tsx b/apps/web/src/routes/social-import.tsx
index b7a63cf12..d88dd87c8 100644
--- a/apps/web/src/routes/social-import.tsx
+++ b/apps/web/src/routes/social-import.tsx
@@ -1032,7 +1032,7 @@ function CommitProgressPanel({ progress }: { progress: CommitProgress }): React.
aria-label="Commit progress"
>
diff --git a/apps/web/src/workbench/TabBreadcrumb.tsx b/apps/web/src/workbench/TabBreadcrumb.tsx
index cc192d3a8..95622faa9 100644
--- a/apps/web/src/workbench/TabBreadcrumb.tsx
+++ b/apps/web/src/workbench/TabBreadcrumb.tsx
@@ -11,6 +11,7 @@
*/
import { FolderSchema } from '@xnetjs/data'
import { useQuery } from '@xnetjs/react'
+import { useViewTransition } from '@xnetjs/ui'
import { FolderClosed, Users } from 'lucide-react'
import { Fragment, useMemo } from 'react'
import { useSpaces } from '../hooks/useSpaces'
@@ -53,6 +54,7 @@ export function TabBreadcrumb({ tab }: { tab: WorkbenchTab | null }) {
const { folderNames, spaceId } = useBreadcrumb(tab)
const { getSpace } = useSpaces()
const setCurrentSpace = useWorkbench((state) => state.setCurrentSpace)
+ const withTransition = useViewTransition()
const space = getSpace(spaceId)
if (!space && folderNames.length === 0) return null
@@ -62,7 +64,7 @@ export function TabBreadcrumb({ tab }: { tab: WorkbenchTab | null }) {
{space ? (