Skip to content

Commit 668d82f

Browse files
committed
refactor(app-shell): move ObjectView's leaf pieces into objectViewChrome.tsx (slice 3)
ObjectNotFoundState, the saved-view tab strip's viewport check (VIEW_TABS_MIN_WIDTH, viewTabsFitNow, useViewTabsFit) and keepInlineEditModeForTheSession move verbatim out of views/ObjectView.tsx into views/objectViewChrome.tsx. ObjectView.tsx imports them back and re-exports ObjectNotFoundState, the one name it exported. No test edits. Claude-Session: https://claude.ai/code/session_01QciHBvFJXnhZqXmdqpDtpz Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9b51e47 commit 668d82f

3 files changed

Lines changed: 118 additions & 89 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
refactor(app-shell): the Console `ObjectView`'s leaf pieces move into a sibling module
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The "object not found" state (`ObjectNotFoundState`), the viewport check that decides whether the saved-view tab strip is drawn, and the inline-edit toggle's session-only callback move verbatim out of `views/ObjectView.tsx` into `objectViewChrome.tsx` beside it. No logic, name or rendered output changes. `ObjectView.tsx` re-exports every name it exported before, and the package entry is unchanged.
11+
12+
What to do: nothing.

‎ui/packages/app-shell/src/views/ObjectView.tsx‎

Lines changed: 6 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import {
4949
EmptyDescription,
5050
NavigationOverlay,
5151
} from '@object-ui/components';
52-
import { Plus, Upload, Table as TableIcon, Lock, MoreHorizontal, RotateCcw } from 'lucide-react';
52+
import { Plus, Upload, Lock, MoreHorizontal, RotateCcw } from 'lucide-react';
5353
import { FavoriteMenuItem, useObjectListFavorite } from '../layout/SidebarItemMenu.js';
5454
import { useMetaItemRefusal } from '../hooks/useMetaItemRefusal.js';
5555
import { useTenancyPosture } from '../hooks/useTenancyPosture.js';
@@ -125,6 +125,7 @@ import {
125125
isSameOptionsValue,
126126
dispatchViewPatches,
127127
} from './objectViewOptions.js';
128+
import { ObjectNotFoundState, useViewTabsFit, keepInlineEditModeForTheSession } from './objectViewChrome.js';
128129

129130
// objectstack#25992 slice 1: these module-level helpers moved to the sibling
130131
// `objectView*.ts` modules above. Every name this file exported stays
@@ -155,6 +156,10 @@ export {
155156
dispatchViewPatches,
156157
} from './objectViewOptions.js';
157158

159+
// objectstack#25992 slice 3: `ObjectNotFoundState` moved to `./objectViewChrome.tsx`
160+
// and stays exported from here (`AppContent` imports it by this path).
161+
export { ObjectNotFoundState } from './objectViewChrome.js';
162+
158163
/**
159164
* Props of the Console `ObjectView`, the route-level object surface
160165
* `@object-ui/app-shell` exports (objectui#7483).
@@ -186,94 +191,6 @@ export interface ConsoleObjectViewProps {
186191
externalRefreshKey?: number;
187192
}
188193

189-
/**
190-
* The "object not found" state the route draws for an object it has no
191-
* definition of. objectui#13052 — `packageDisabled`: the server answered that
192-
* the object exists and its package is disabled (`OBJECT_PACKAGE_DISABLED`),
193-
* so the description says that instead. The console route draws it for that
194-
* answer (`AppContent`); this module's `ObjectView` draws it for an absence.
195-
* Not part of the package entry.
196-
*/
197-
export function ObjectNotFoundState({ objectName, packageDisabled = false }: { objectName: string | undefined; packageDisabled?: boolean }) {
198-
const { t } = useObjectTranslation();
199-
return (
200-
<div className="h-full p-4 flex items-center justify-center">
201-
<Empty data-testid={packageDisabled ? 'object-package-disabled' : 'object-not-found'}>
202-
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
203-
<TableIcon className="h-6 w-6 text-muted-foreground" />
204-
</div>
205-
<EmptyTitle>{t('console.objectView.objectNotFound')}</EmptyTitle>
206-
<EmptyDescription>
207-
{packageDisabled
208-
? t('console.objectView.objectPackageDisabled', { objectName })
209-
: t('console.objectView.objectNotFoundDescription', { objectName })}
210-
{' '}
211-
{t('console.objectView.objectNotFoundHint')}
212-
</EmptyDescription>
213-
</Empty>
214-
</div>
215-
);
216-
}
217-
218-
/**
219-
* The list toolbar's inline-edit toggle, as this page wires it: it writes
220-
* nothing (objectui#5144, triage's ruling E).
221-
*
222-
* The view's `inlineEdit` and `userActions.editInline` are the AUTHOR's
223-
* permission keys, and `normalizeListViewSchema` folds the first into the
224-
* second. `editInline` is on by default since the v18 ruling (objectstack#22605,
225-
* objectui#12086), so a view that declares neither key offers the toggle, and
226-
* `editInline: false` withdraws it. This toggle used to persist a USER's edit
227-
* mode into `inlineEdit` through `persistViewPatch`. After the fold, switching
228-
* it off stored `inlineEdit: false`, which reads as "not offered", so the
229-
* toggle was gone from the next load and nothing in the console could bring it
230-
* back.
231-
*
232-
* The edit mode is now session state. `ListView` keeps it, and seeds it from
233-
* the view's `inlineEdit` on each load. The callback stays wired because
234-
* `ListView` offers the wide toolbar toggle only to a host that wires one.
235-
*
236-
* Recorded costs: the edit mode is not remembered across loads, and an overlay
237-
* that already stores `inlineEdit: false` still reads off. That is existing
238-
* data, and the maintainer's ruling rejects migrating it. Declaring
239-
* `userActions.editInline: true` on such a view brings the toggle back.
240-
*/
241-
/**
242-
* Tailwind's `sm` breakpoint (px): from here up the list page draws its
243-
* saved-view tabs in the list toolbar; below it the app bar's mobile view
244-
* switcher is the page's only view control (objectui#12440).
245-
*/
246-
const VIEW_TABS_MIN_WIDTH = 640;
247-
248-
/**
249-
* Is the viewport at least {@link VIEW_TABS_MIN_WIDTH} wide? The matchMedia
250-
* idiom of `useIsMobile` / `useIsWideViewport`, except the first answer is
251-
* read synchronously: the tab strip is the first thing on the page's list row,
252-
* and resolving it in an effect would draw the row once without it and then
253-
* jump. objectui#12440 — this decides whether the strip is HANDED to the list
254-
* at all, so below `sm` nothing is passed and nothing is drawn (the CSS
255-
* `hidden sm:block` it replaces only hid it).
256-
*/
257-
function viewTabsFitNow(): boolean {
258-
return typeof window !== 'undefined' && window.innerWidth >= VIEW_TABS_MIN_WIDTH;
259-
}
260-
261-
function useViewTabsFit(): boolean {
262-
const [fits, setFits] = useState<boolean>(viewTabsFitNow);
263-
useEffect(() => {
264-
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
265-
const mql = window.matchMedia(`(min-width: ${VIEW_TABS_MIN_WIDTH}px)`);
266-
const onChange = () => setFits(viewTabsFitNow());
267-
mql.addEventListener('change', onChange);
268-
return () => mql.removeEventListener('change', onChange);
269-
}, []);
270-
return fits;
271-
}
272-
273-
function keepInlineEditModeForTheSession(): void {
274-
// Deliberately empty: see the docblock.
275-
}
276-
277194
export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey }: ConsoleObjectViewProps) {
278195
const { objectName } = useParams();
279196

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Leaf pieces of the Console `ObjectView` (`./ObjectView.tsx`): the "object
3+
* not found" state, the viewport check that decides whether the saved-view tab
4+
* strip is drawn, and the inline-edit toggle's session-only callback, moved out
5+
* of that file verbatim (objectstack#25992, slice 3). `ObjectView.tsx`
6+
* re-exports every name it exported before.
7+
*/
8+
9+
import { useState, useEffect } from 'react';
10+
import { Empty, EmptyTitle, EmptyDescription } from '@object-ui/components';
11+
import { Table as TableIcon } from 'lucide-react';
12+
import { useObjectTranslation } from '@object-ui/i18n';
13+
14+
/**
15+
* The "object not found" state the route draws for an object it has no
16+
* definition of. objectui#13052 — `packageDisabled`: the server answered that
17+
* the object exists and its package is disabled (`OBJECT_PACKAGE_DISABLED`),
18+
* so the description says that instead. The console route draws it for that
19+
* answer (`AppContent`); this module's `ObjectView` draws it for an absence.
20+
* Not part of the package entry.
21+
*/
22+
export function ObjectNotFoundState({ objectName, packageDisabled = false }: { objectName: string | undefined; packageDisabled?: boolean }) {
23+
const { t } = useObjectTranslation();
24+
return (
25+
<div className="h-full p-4 flex items-center justify-center">
26+
<Empty data-testid={packageDisabled ? 'object-package-disabled' : 'object-not-found'}>
27+
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
28+
<TableIcon className="h-6 w-6 text-muted-foreground" />
29+
</div>
30+
<EmptyTitle>{t('console.objectView.objectNotFound')}</EmptyTitle>
31+
<EmptyDescription>
32+
{packageDisabled
33+
? t('console.objectView.objectPackageDisabled', { objectName })
34+
: t('console.objectView.objectNotFoundDescription', { objectName })}
35+
{' '}
36+
{t('console.objectView.objectNotFoundHint')}
37+
</EmptyDescription>
38+
</Empty>
39+
</div>
40+
);
41+
}
42+
43+
/**
44+
* The list toolbar's inline-edit toggle, as this page wires it: it writes
45+
* nothing (objectui#5144, triage's ruling E).
46+
*
47+
* The view's `inlineEdit` and `userActions.editInline` are the AUTHOR's
48+
* permission keys, and `normalizeListViewSchema` folds the first into the
49+
* second. `editInline` is on by default since the v18 ruling (objectstack#22605,
50+
* objectui#12086), so a view that declares neither key offers the toggle, and
51+
* `editInline: false` withdraws it. This toggle used to persist a USER's edit
52+
* mode into `inlineEdit` through `persistViewPatch`. After the fold, switching
53+
* it off stored `inlineEdit: false`, which reads as "not offered", so the
54+
* toggle was gone from the next load and nothing in the console could bring it
55+
* back.
56+
*
57+
* The edit mode is now session state. `ListView` keeps it, and seeds it from
58+
* the view's `inlineEdit` on each load. The callback stays wired because
59+
* `ListView` offers the wide toolbar toggle only to a host that wires one.
60+
*
61+
* Recorded costs: the edit mode is not remembered across loads, and an overlay
62+
* that already stores `inlineEdit: false` still reads off. That is existing
63+
* data, and the maintainer's ruling rejects migrating it. Declaring
64+
* `userActions.editInline: true` on such a view brings the toggle back.
65+
*/
66+
/**
67+
* Tailwind's `sm` breakpoint (px): from here up the list page draws its
68+
* saved-view tabs in the list toolbar; below it the app bar's mobile view
69+
* switcher is the page's only view control (objectui#12440).
70+
*/
71+
const VIEW_TABS_MIN_WIDTH = 640;
72+
73+
/**
74+
* Is the viewport at least {@link VIEW_TABS_MIN_WIDTH} wide? The matchMedia
75+
* idiom of `useIsMobile` / `useIsWideViewport`, except the first answer is
76+
* read synchronously: the tab strip is the first thing on the page's list row,
77+
* and resolving it in an effect would draw the row once without it and then
78+
* jump. objectui#12440 — this decides whether the strip is HANDED to the list
79+
* at all, so below `sm` nothing is passed and nothing is drawn (the CSS
80+
* `hidden sm:block` it replaces only hid it).
81+
*/
82+
function viewTabsFitNow(): boolean {
83+
return typeof window !== 'undefined' && window.innerWidth >= VIEW_TABS_MIN_WIDTH;
84+
}
85+
86+
export function useViewTabsFit(): boolean {
87+
const [fits, setFits] = useState<boolean>(viewTabsFitNow);
88+
useEffect(() => {
89+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
90+
const mql = window.matchMedia(`(min-width: ${VIEW_TABS_MIN_WIDTH}px)`);
91+
const onChange = () => setFits(viewTabsFitNow());
92+
mql.addEventListener('change', onChange);
93+
return () => mql.removeEventListener('change', onChange);
94+
}, []);
95+
return fits;
96+
}
97+
98+
export function keepInlineEditModeForTheSession(): void {
99+
// Deliberately empty: see the docblock.
100+
}

0 commit comments

Comments
 (0)