invoices, extracted into plugins/invoices — the platform half
Ninth extraction, third resurrection. Switched off since 2026-08-13 with the permission reduced to api: [], so assertPermissionTotality was satisfied, the server booted clean, and every panel request 404'd. Closes the same data hazard as jellyfin and soulseek: invoiceshelf/schema was commented out of the drizzle barrel. Named `invoices`, not `invoiceshelf`. The appName is THE id — API prefix, frontend route, table prefix, sidecar suffix, install key — and before extraction the API (/api/invoiceshelf) and the screen (/invoices) had drifted apart, which was harmless only while they were separate hardcoded strings. The route is the part a person sees, and the permission key and label were already `invoices`, so the API prefix was the odd one out. Cost three call sites and a table rename to invoices_accounts, which was free only because the table existed on no server — verified before deciding, not after. members.test.ts re-anchored again, this time to caldav and memos: services with no extraction planned. That assertion has now been re-pointed four times by services leaving for plugins/, which is the whole reason installer.test.ts was made literal earlier tonight. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,8 +107,6 @@ export function App() {
|
||||
<Route path="/gitea/repo/:owner/:name" element={<Dashboard.GiteaScreen />} />
|
||||
<Route path="/gitea/repo/:owner/:name/:tab" element={<Dashboard.GiteaScreen />} />
|
||||
<Route path="/gitea/repo/:owner/:name/:tab/:item" element={<Dashboard.GiteaScreen />} />
|
||||
<Route path="/invoices" element={<Dashboard.InvoicesScreen />} />
|
||||
<Route path="/invoices/:section" element={<Dashboard.InvoicesScreen />} />
|
||||
<Route path="/wallet" element={<Dashboard.WalletScreen />} />
|
||||
<Route path="/wallet/:section" element={<Dashboard.WalletScreen />} />
|
||||
<Route path="/system-monitor" element={<Dashboard.SystemMonitorScreen />} />
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { LayoutNode } from 'officerdev';
|
||||
import { Navigate, useParams } from 'react-router';
|
||||
import { DEFAULT_INVOICES_SECTION, invoicesSectionPath, isInvoicesSection, WorkspaceView } from 'officerdev';
|
||||
import { useDashboardState } from 'state/useDashboardState';
|
||||
import { defaultLayout } from './defaultLayout';
|
||||
|
||||
// /invoices uses the Workspace/Panel system (like /command-hq): the section nav on the
|
||||
// left, the section itself on the right. Both panels talk to the officer-invoiceshelf sidecar through the
|
||||
// /api/invoiceshelf auth proxy — the InvoiceShelf URL, its Sanctum token and the company header live in the
|
||||
// sidecar and never reach the browser.
|
||||
//
|
||||
// The open section is :section in the URL; list filters, the selected record and the open editor are query
|
||||
// params. So the panels read the URL rather than passing state to each other, and this screen is the single
|
||||
// place that decides what an absent or bogus section means.
|
||||
|
||||
export const InvoicesScreen = () => {
|
||||
const { section } = useParams();
|
||||
const workspace = useDashboardState<LayoutNode>('screens/invoices', defaultLayout);
|
||||
|
||||
// Bare /invoices, or a section that doesn't exist, resolves to a canonical URL rather than rendering a
|
||||
// default while the address bar says something else — the nav highlight is derived from the URL.
|
||||
if (!isInvoicesSection(section)) {
|
||||
return <Navigate to={invoicesSectionPath(DEFAULT_INVOICES_SECTION)} replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceView
|
||||
workspace={workspace}
|
||||
locked
|
||||
appTypes={{ allowed: ['invoices-nav', 'invoices-view'], fallback: 'invoices-view' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { LayoutNode } from 'officerdev';
|
||||
|
||||
export const defaultLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'invoices-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'invoices-nav', appType: 'invoices-nav' }, size: 20 },
|
||||
{ node: { type: 'panel', id: 'invoices-view', appType: 'invoices-view' }, size: 80 },
|
||||
],
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './InvoicesScreen';
|
||||
@@ -15,7 +15,6 @@ export * from './Calendar';
|
||||
export * from './Contacts';
|
||||
export * from './CommandHQ';
|
||||
export * from './Gitea';
|
||||
export * from './Invoices';
|
||||
export * from './Wallet';
|
||||
export * from './SystemMonitor';
|
||||
export * from './Activity';
|
||||
|
||||
@@ -25,7 +25,6 @@ const RULES: TitleRule[] = [
|
||||
{ match: (p) => p.startsWith('/plugins'), title: 'Marketplace' },
|
||||
{ match: (p) => p.startsWith('/command-hq'), title: 'Command HQ' },
|
||||
{ match: (p) => p.startsWith('/gitea'), title: 'Gitea' },
|
||||
{ match: (p) => p.startsWith('/invoices'), title: 'Invoices' },
|
||||
{ match: (p) => p.startsWith('/wallet'), title: 'Wallet' },
|
||||
{ match: (p) => p.startsWith('/system-monitor'), title: 'System Monitor' },
|
||||
{ match: (p) => p.startsWith('/qr-transfer'), title: 'QR Transfer' },
|
||||
|
||||
Reference in New Issue
Block a user