fix(settings): accurate View navigation after restore in recently deleted (#4546)

* fix(settings): accurate View navigation after restore in recently deleted

Files now deep-link to /files/{id} (was /files), and folders expand
the restored folder + its parent chain in the sidebar before navigating
to /w so the user actually lands on the item they restored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: fall back to archived folders for parent-chain lookup

The restored folder may not be in the active folders cache yet when
View is clicked (the invalidation+refetch fires from onSettled, after
onSuccess surfaces the View button). Merge archived folder data — where
the restored item still lives — into the lookup map so the expansion
loop can always resolve the folder and walk its parent chain.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(styling): canonicalize size-* shorthand for equal height/width

Document the size-* shorthand as the canonical pattern across CLAUDE.md,
AGENTS.md, .claude rules, .cursor + .agents commands, and the emcn
design-review skill. Default icon size is size-[14px]. Treat
h-[Npx] w-[Npx] and h-N w-N pairs as refactor targets.

Also migrate the remaining occurrences in recently-deleted.tsx.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-05-10 13:12:08 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 3ed8615b5d
commit 10f7d36f13
8 changed files with 62 additions and 37 deletions
+14 -11
View File
@@ -234,7 +234,7 @@ Use for context menus and action menus:
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<MoreHorizontal className="h-[14px] w-[14px]" />
<MoreHorizontal className="size-[14px]" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
@@ -281,19 +281,21 @@ Rules:
- Stack multiple skeletons for lists
### Icons
Standard sizing — `h-[14px] w-[14px]` is the dominant pattern (400+ uses):
Standard sizing — use the `size-*` shorthand. `size-[14px]` is the dominant pattern:
```tsx
<Icon className="h-[14px] w-[14px] text-[var(--text-icon)]" />
<Icon className="size-[14px] text-[var(--text-icon)]" />
```
Size scale by frequency:
1. `h-[14px] w-[14px]` — default for inline icons (most common)
2. `h-[16px] w-[16px]` — slightly larger inline icons
3. `h-3 w-3` (12px) — compact/tight spaces
4. `h-4 w-4` (16px) — Tailwind equivalent, also common
5. `h-3.5 w-3.5` (14px) — Tailwind equivalent of 14px
6. `h-5 w-5` (20px) — larger icons, section headers
Always prefer `size-*` over the legacy `h-* w-*` pair. `size-[14px]` is canonical; treat any `h-[Npx] w-[Npx]` or `h-N w-N` pair as a refactor target.
Size scale (most common first):
1. `size-[14px]` — default for inline icons
2. `size-[16px]` — slightly larger inline icons
3. `size-3` (12px) — compact/tight spaces
4. `size-4` (16px) — Tailwind equivalent
5. `size-3.5` (14px) — Tailwind equivalent of 14px
6. `size-5` (20px) — larger icons, section headers
Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).
@@ -332,4 +334,5 @@ Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).
- Importing from emcn subpaths instead of barrel export
- Using arbitrary z-index (`z-50`, `z-[9999]`) instead of z-index tokens
- Custom shadows instead of shadow tokens
- Icon sizes that don't follow the established scale (default to `h-[14px] w-[14px]`)
- Icon sizes that don't follow the established scale (default to `size-[14px]`)
- Splitting equal height/width into `h-* w-*` pairs instead of the `size-*` shorthand
+1 -1
View File
@@ -65,7 +65,7 @@ Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` act
## Icons
Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
Default: `size-[14px]`. Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px. Use the `size-*` shorthand — flag `h-[Npx] w-[Npx]` and `h-N w-N` pairs as refactor targets.
## Anti-patterns to flag
+1
View File
@@ -32,4 +32,5 @@ function Label({ className, ...props }) {
- Export component and variants (if using CVA)
- TSDoc with usage examples
- Consistent tokens: `font-medium`, `text-[12px]`, `rounded-[4px]`
- Equal height/width → `size-*` (e.g. `size-[14px]`, `size-4`), never `h-[Npx] w-[Npx]` or `h-N w-N`. Default icon size is `size-[14px]`
- `transition-colors` for hover states
+2 -1
View File
@@ -11,7 +11,8 @@ paths:
1. **No inline styles** - Use Tailwind classes
2. **No duplicate dark classes** - Skip `dark:` when value matches light mode
3. **Exact values** - `text-[14px]`, `h-[26px]`
4. **Transitions** - `transition-colors` for interactive states
4. **Equal h/w → `size-*`** - Use `size-[14px]` / `size-4`, never `h-[14px] w-[14px]` or `h-4 w-4`. Default icon size is `size-[14px]`
5. **Transitions** - `transition-colors` for interactive states
## Conditional Classes
+1 -1
View File
@@ -60,7 +60,7 @@ Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` act
## Icons
Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
Default: `size-[14px]`. Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px. Use the `size-*` shorthand — flag `h-[Npx] w-[Npx]` and `h-N w-N` pairs as refactor targets.
## Anti-patterns to flag
+6
View File
@@ -357,6 +357,12 @@ Use Tailwind only, no inline styles. Use `cn()` from `@/lib/utils` for condition
<div className={cn('base-classes', isActive && 'active-classes')} />
```
For equal height and width, use the `size-*` shorthand — never `h-[Npx] w-[Npx]` or `h-N w-N`. Default icon size is `size-[14px]`.
```typescript
<Icon className='size-[14px] text-[var(--text-icon)]' />
```
## EMCN Components
Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
+6
View File
@@ -347,6 +347,12 @@ Use Tailwind only, no inline styles. Use `cn()` from `@/lib/utils` for condition
<div className={cn('base-classes', isActive && 'active-classes')} />
```
For equal height and width, use the `size-*` shorthand — never `h-[Npx] w-[Npx]` or `h-N w-N`. Default icon size is `size-[14px]`.
```typescript
<Icon className='size-[14px] text-[var(--text-icon)]' />
```
## EMCN Components
Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
@@ -1,6 +1,7 @@
'use client'
import { useMemo, useState } from 'react'
import { toError } from '@sim/utils/errors'
import { formatDate } from '@sim/utils/formatting'
import { Folder, Search } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
@@ -21,6 +22,10 @@ import { useKnowledgeBasesQuery, useRestoreKnowledgeBase } from '@/hooks/queries
import { useRestoreTable, useTablesList } from '@/hooks/queries/tables'
import { useRestoreWorkflow, useWorkflows } from '@/hooks/queries/workflows'
import { useRestoreWorkspaceFile, useWorkspaceFiles } from '@/hooks/queries/workspace-files'
import { useFolderStore } from '@/stores/folders/store'
import type { WorkflowFolder } from '@/stores/folders/types'
type ResourceType = 'all' | 'workflow' | 'table' | 'knowledge' | 'file' | 'folder'
function getResourceHref(
workspaceId: string,
@@ -36,14 +41,12 @@ function getResourceHref(
case 'knowledge':
return `${base}/knowledge/${id}`
case 'file':
return `${base}/files`
return `${base}/files/${id}`
case 'folder':
return `${base}/w`
}
}
type ResourceType = 'all' | 'workflow' | 'table' | 'knowledge' | 'file' | 'folder'
type SortColumn = 'deleted' | 'name' | 'type'
interface SortConfig {
@@ -59,7 +62,7 @@ const SORT_OPTIONS: { column: SortColumn; direction: 'asc' | 'desc'; label: stri
{ column: 'type', direction: 'asc', label: 'Type (A–Z)' },
]
const ICON_CLASS = 'h-[14px] w-[14px]'
const ICON_CLASS = 'size-[14px]'
const RESOURCE_TYPE_TO_MOTHERSHIP: Partial<
Record<Exclude<ResourceType, 'all'>, MothershipResourceType>
@@ -119,13 +122,9 @@ function ResourceIcon({ resource }: { resource: DeletedResource }) {
const mothershipType = RESOURCE_TYPE_TO_MOTHERSHIP[resource.type]
if (!mothershipType) return null
const config = RESOURCE_REGISTRY[mothershipType]
return (
<>
{config.renderTabIcon(
{ type: mothershipType, id: resource.id, title: resource.name },
ICON_CLASS
)}
</>
return config.renderTabIcon(
{ type: mothershipType, id: resource.id, title: resource.name },
ICON_CLASS
)
}
@@ -141,6 +140,7 @@ export function RecentlyDeleted() {
const workflowsQuery = useWorkflows(workspaceId, { scope: 'archived' })
const foldersQuery = useFolders(workspaceId, { scope: 'archived' })
const activeFoldersQuery = useFolders(workspaceId)
const tablesQuery = useTablesList(workspaceId, 'archived')
const knowledgeQuery = useKnowledgeBasesQuery(workspaceId, { scope: 'archived' })
const filesQuery = useWorkspaceFiles(workspaceId, 'archived')
@@ -220,7 +220,6 @@ export function RecentlyDeleted() {
})
}
// Merge back restored items that are no longer in the query data
const itemIds = new Set(items.map((i) => i.id))
for (const [id, resource] of restoredItems) {
if (!itemIds.has(id)) {
@@ -267,6 +266,23 @@ export function RecentlyDeleted() {
const showNoResults = searchTerm.trim() && filtered.length === 0 && resources.length > 0
const selectedSort = activeSort ?? DEFAULT_SORT
function handleView(resource: DeletedResource) {
if (resource.type === 'folder') {
const setExpanded = useFolderStore.getState().setExpanded
const byId = new Map<string, WorkflowFolder>()
for (const folder of foldersQuery.data ?? []) byId.set(folder.id, folder)
for (const folder of activeFoldersQuery.data ?? []) byId.set(folder.id, folder)
let current: WorkflowFolder | undefined = byId.get(resource.id)
const seen = new Set<string>()
while (current && !seen.has(current.id)) {
seen.add(current.id)
setExpanded(current.id, true)
current = current.parentId ? byId.get(current.parentId) : undefined
}
}
router.push(getResourceHref(resource.workspaceId, resource.type, resource.id))
}
function handleRestore(resource: DeletedResource) {
setRestoringIds((prev) => new Set(prev).add(resource.id))
@@ -363,7 +379,7 @@ export function RecentlyDeleted() {
{error ? (
<div className='flex h-full flex-col items-center justify-center gap-2'>
<p className='text-[var(--text-error)] text-xs leading-tight'>
{error instanceof Error ? error.message : 'Failed to load deleted items'}
{toError(error).message || 'Failed to load deleted items'}
</p>
</div>
) : isLoading ? (
@@ -387,7 +403,7 @@ export function RecentlyDeleted() {
return (
<div
key={resource.id}
className='flex items-center gap-3 rounded-md p-2 hover-hover:bg-[var(--bg-hover)]'
className='flex items-center gap-3 rounded-md px-2 py-2 hover-hover:bg-[var(--bg-hover)]'
>
<ResourceIcon resource={resource} />
@@ -405,15 +421,7 @@ export function RecentlyDeleted() {
{isRestored ? (
<div className='flex shrink-0 items-center gap-2'>
<span className='text-[var(--text-tertiary)] text-small'>Restored</span>
<Button
variant='primary'
size='sm'
onClick={() =>
router.push(
getResourceHref(resource.workspaceId, resource.type, resource.id)
)
}
>
<Button variant='primary' size='sm' onClick={() => handleView(resource)}>
View
</Button>
</div>