diff --git a/README.md b/README.md
index 22b8d85..53809f4 100644
--- a/README.md
+++ b/README.md
@@ -228,6 +228,8 @@ Statuses are looked up case-insensitively, and an unmapped value renders as a ne
the humanised raw string — so a missing entry is visible rather than silent. Add states there, never
in a page-local table.
+`PageHeader` renders clean icon, title, and action slots across all console pages without explanatory descriptive subtitles, eliminating vertical clutter for operators who already know the console.
+
## Environment
| Variable | Purpose |
diff --git a/src/App.jsx b/src/App.jsx
index b139acb..3f80f5f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -86,8 +86,26 @@ const AuthenticatedApp = () => {
} />
} />
- } />
- } />
+ {/* Control X — the board formerly routed at /doormile/dispatch.
+
+ The directory, the component and the .dispatch-container CSS
+ namespace keep their old names on purpose: they are internal,
+ and renaming a 5,800-line stylesheet's root class is a large
+ diff with no user-visible benefit and real risk of missing a
+ selector. Only what an operator sees and types changes. */}
+ } />
+ } />
+
+ {/* The old paths keep working.
+
+ This board is the page operators sit on all day, so it is the
+ one most likely to be bookmarked, pinned in a browser tab
+ group, or pasted into a message. Renaming the route without
+ this would turn every one of those into a 404 — a support
+ problem with no upside, avoided by two lines. `replace` so the
+ dead path does not linger in history behind a Back press. */}
+ } />
+ } />
{/* Static before dynamic, so `orders/create` cannot be read as an
order whose id is "create". */}
diff --git a/src/components/assistant/DoormileAI/pageContext.jsx b/src/components/assistant/DoormileAI/pageContext.jsx
index 45f7e09..7dee9d8 100644
--- a/src/components/assistant/DoormileAI/pageContext.jsx
+++ b/src/components/assistant/DoormileAI/pageContext.jsx
@@ -154,7 +154,7 @@ const HUBS = {
};
const DISPATCH = {
- label: 'Dispatch',
+ label: 'Control X',
suggestions: [
{ icon: LuLayers, text: 'Morning Batch orders today' },
{ icon: LuTimerOff, text: 'Which orders are delayed?' },
@@ -281,7 +281,7 @@ const FOLLOW_UPS = {
// Longest-prefix first so 'orders/create' doesn't fall through to 'orders'
// with the wrong label.
const ROUTES = [
- ['/doormile/dispatch', DISPATCH],
+ ['/doormile/control-x', DISPATCH],
['/doormile/deliveries', { ...ORDERS, label: 'Deliveries' }],
['/doormile/orders', ORDERS],
['/doormile/milers', RIDERS],
diff --git a/src/components/doormile/BatchEfficiency.jsx b/src/components/doormile/BatchEfficiency.jsx
index d8d4f0f..dbbc691 100644
--- a/src/components/doormile/BatchEfficiency.jsx
+++ b/src/components/doormile/BatchEfficiency.jsx
@@ -81,7 +81,7 @@ export function BatchEfficiency({ batch, orders = [], tenantId }) {
) : entries.length === 0 ? (
/* A response that is not a flat object — an array, a scalar, an error
envelope. Shown raw rather than dropped. */
-
+
{JSON.stringify(result, null, 2)}
) : (
diff --git a/src/components/ds/DataTable.jsx b/src/components/ds/DataTable.jsx
index e291924..b463d4c 100644
--- a/src/components/ds/DataTable.jsx
+++ b/src/components/ds/DataTable.jsx
@@ -170,7 +170,7 @@ export function DataTable({
if (loading) {
return (
-
+
);
@@ -178,7 +178,7 @@ export function DataTable({
if (sortedRows.length === 0) {
return (
-
+
{emptyState || (
-
+ {/* radius="md" (12px), down from "xl".
+
+ Surface's xl resolves to rounded-2xl — calc(var(--radius) + 8px) —
+ which with --radius at 0.75rem is a 20px corner. On a small chip that
+ reads as soft; on a full-width table it curves far enough in to cut
+ visibly across the first and last cells of the header row.
+
+ 12px is `--radius` itself, and matches radiusCard in themes/dt/tokens
+ — the token meant for a large boxed region, which is what a table is.
+ A value picked off the scale rather than an arbitrary px, so it stays
+ in step if the base radius is ever retuned.
+
+ Set on all three branches (loading, empty, populated) so a table does
+ not change shape as its data arrives. */}
+
-
}
+
+ )}
{children}
diff --git a/src/components/ds/Tabs.jsx b/src/components/ds/Tabs.jsx
index 1ac9811..ccc4ec7 100644
--- a/src/components/ds/Tabs.jsx
+++ b/src/components/ds/Tabs.jsx
@@ -85,7 +85,15 @@ export function Tabs({
// parent — without it a flex item refuses to go below its content width and
// overflows the page instead of scrolling inside itself.
const track = (children) => (
-
+
{children}
);
diff --git a/src/components/nearle_components/CLAUDE.md b/src/components/nearle_components/CLAUDE.md
index 8f29d9b..3cf0529 100644
--- a/src/components/nearle_components/CLAUDE.md
+++ b/src/components/nearle_components/CLAUDE.md
@@ -59,7 +59,7 @@ inconsistent.
import { PageShell, KpiGrid, FilterToolbar, DataCard } from 'components/nearle_components/PageLayout';
{/* gap={6} between regions */}
- } />
+ } />
…… {/* fills the row, no dead gap */}
…selectors, date, search…}
diff --git a/src/globalPolish.css b/src/globalPolish.css
index 68a2ed6..45fc2d7 100644
--- a/src/globalPolish.css
+++ b/src/globalPolish.css
@@ -55,6 +55,36 @@ body {
height: 8px;
}
+/* The four ::-webkit-scrollbar rules around this comment are DEAD in current
+ browsers. Measured, not assumed — Chrome 152, this app's own stylesheet
+ loaded, on an element that scrolls horizontally only:
+
+ inherits `* { scrollbar-width: thin }` -> 10px bar
+ scrollbar-width: auto -> 15px bar
+ scrollbar-width: none -> 0px
+
+ Neither the 8px sizing above nor a `::-webkit-scrollbar:horizontal
+ { height: 0 }` had any effect. Once an element resolves the STANDARD
+ `scrollbar-width` property — which the `*` rule above hands every element
+ in the app — Chrome takes the standard path and ignores the legacy
+ pseudo-elements entirely. So the console's scrollbars are drawn by
+ `scrollbar-width: thin` + `scrollbar-color`, and the webkit block is inert.
+ Kept for older WebKit rather than deleted, but do not reach for it to
+ change anything: it will look like it should work and will not.
+
+ What this means for hiding the SIDEWAYS bar only: the standard property has
+ no per-axis form, and the webkit `:horizontal` selector that would provide
+ one is ignored. A bar can therefore only be hidden per-ELEMENT, with
+ `scrollbar-width: none` — the `.no-scrollbar` utility in index.css. It is
+ applied to the containers that scroll horizontally ONLY, where there is no
+ vertical bar for it to take away: every DataTable, every tab strip, the
+ profitability and batch-efficiency tables, and Dispatch's batch scroller,
+ strat row and compare timeline.
+
+ One container scrolls on both axes — the bulk-upload preview in
+ MultipleOrders — and keeps both bars deliberately. Hiding its horizontal
+ bar is not possible without hiding the vertical one the operator needs. */
+
*::-webkit-scrollbar-track {
background-color: transparent;
}
diff --git a/src/layouts/AdminLayout.jsx b/src/layouts/AdminLayout.jsx
index 74dd9a5..4ca2d41 100644
--- a/src/layouts/AdminLayout.jsx
+++ b/src/layouts/AdminLayout.jsx
@@ -47,7 +47,7 @@ const LOGO_FILTER = {
/** Destinations that sit directly on the header bar. */
const NAV = [
- { label: 'Dispatch', path: '/doormile/dispatch' },
+ { label: 'Control X', path: '/doormile/control-x' },
{ label: 'Bookings', path: '/doormile/bookings' },
{ label: 'Orders', path: '/doormile/orders' },
{ label: 'Deliveries', path: '/doormile/deliveries' },
@@ -60,21 +60,21 @@ const NAV_GROUPS = [
{
label: 'Fleet Ops',
items: [
- { label: 'Hubs', path: '/doormile/hubs', icon: Warehouse, desc: 'Sortation centers & facilities' },
- { label: 'Vehicles', path: '/doormile/vehicles', icon: Car, desc: 'Fleet register & EV telematics' },
- { label: 'Tripsheets', path: '/doormile/tripsheets', icon: ListTodo, desc: 'Linehaul & middle-mile runs' },
- { label: 'Exceptions', path: '/doormile/exceptions', icon: ShieldAlert, desc: 'Live alerts & delivery blockers' },
- { label: 'Competitive Intel', path: '/doormile/competitive-intel', icon: Activity, desc: 'Benchmarking & market rates' },
- { label: 'App Users', path: '/doormile/app-users', icon: UserCheck, desc: 'System operators & roles' },
+ { label: 'Hubs', path: '/doormile/hubs', icon: Warehouse },
+ { label: 'Vehicles', path: '/doormile/vehicles', icon: Car },
+ { label: 'Tripsheets', path: '/doormile/tripsheets', icon: ListTodo },
+ { label: 'Exceptions', path: '/doormile/exceptions', icon: ShieldAlert },
+ { label: 'Competitive Intel', path: '/doormile/competitive-intel', icon: Activity },
+ { label: 'App Users', path: '/doormile/app-users', icon: UserCheck },
],
},
{
label: 'Reports',
items: [
- { label: 'Orders Summary', path: '/doormile/reports/orderssummary', icon: FileSpreadsheet, desc: 'Daily volume & status aggregations' },
- { label: 'Orders Details', path: '/doormile/reports/ordersdetails', icon: FileText, desc: 'Granular leg-by-leg breakdown' },
- { label: 'Milers Summary', path: '/doormile/reports/riderssummary', icon: Bike, desc: 'Fleet KPIs & on-duty shifts' },
- { label: 'Profitability', path: '/doormile/reports/profitability', icon: Coins, desc: 'Gross margin & variable costs' },
+ { label: 'Orders Summary', path: '/doormile/reports/orderssummary', icon: FileSpreadsheet },
+ { label: 'Orders Details', path: '/doormile/reports/ordersdetails', icon: FileText },
+ { label: 'Milers Summary', path: '/doormile/reports/riderssummary', icon: Bike },
+ { label: 'Profitability', path: '/doormile/reports/profitability', icon: Coins },
],
},
];
@@ -372,28 +372,31 @@ export default function AdminLayout() {
key={item.path}
onClick={() => navigate(item.path)}
className={cn(
- 'flex items-start gap-2.5 p-2 rounded-xl cursor-pointer transition-colors my-0.5',
+ // items-center, not items-start: with the second
+ // line gone there is nothing to top-align to, and
+ // the icon would sit high against a single label.
+ 'flex items-center gap-2.5 p-2 rounded-xl cursor-pointer transition-colors my-0.5',
active ? 'bg-brand-tint/80 text-brand' : 'hover:bg-slate-50 text-ink-1'
)}
>
{ItemIcon && (
)}
-
-
{item.label}
- {item.desc && (
-
- {item.desc}
-
- )}
-
+ {/* Label only. The descriptions under each item
+ ("Sortation centers & facilities", "Linehaul &
+ middle-mile runs") were removed: this is a jump
+ menu for people who already know the console, so a
+ gloss on every row is read once and then skipped
+ past forever while still costing the height that
+ made the menu tall enough to cover the page. */}
+
{item.label}
);
})}
@@ -418,17 +421,25 @@ export default function AdminLayout() {
assistantOpen ? 'bg-brand-tint text-brand' : 'text-ink-3 hover:bg-surface-sunken hover:text-ink-1'
)}
>
- {/* 20px inside a 32px button.
- Was `h-9 w-9 max-w-none` — a 36px image in a 32px button,
- so the mark overflowed its own control by 2px on every
- side. Two visible consequences: it stood proud of the
- notification bell and avatar beside it, breaking the row's
- alignment, and it covered the button's own area completely,
- so the `hover:bg-surface-sunken` tint had nowhere to show —
- the control looked dead on hover while its neighbours lit
- up. Sized to sit inside the button, like the Bell's 16px
- icon does. */}
-
+ {/* 24px inside a 32px button.
+
+ Started at `h-9 w-9 max-w-none` — a 36px image in a 32px
+ button, so the mark overflowed its own control by 2px on
+ every side. That broke the row's alignment against the
+ bell and avatar AND covered the button completely, leaving
+ `hover:bg-surface-sunken` nowhere to render, so the control
+ looked dead on hover while its neighbours lit up.
+
+ Dropped to 20px to fix both, which read as too faint next
+ to the bell. 24px is the ceiling that keeps the fix: it
+ leaves 4px of button on every side, which is what the
+ hover tint needs to be visible as a ring around the mark.
+ Anything larger and the tint disappears again.
+
+ Bigger than the Bell's 16px on purpose — this is a brand
+ mark carrying its own circular frame, so it reads smaller
+ than its box, where a stroked icon reads full-size. */}
+
)}
@@ -554,10 +565,10 @@ export default function AdminLayout() {
{/* `dai-page` is the handshake with the assistant dock: while the panel
is open, DoormileAI.css reserves exactly its width of padding here so
the page slides open beside it instead of being covered. */}
-