This commit is contained in:
2026-09-28 19:51:04 +05:30
parent 69d10975b3
commit adaac48e9b
2 changed files with 35 additions and 171 deletions

View File

@@ -14,14 +14,8 @@ component and look identical on screen, down to the heading. The only difference
is the pathname, which is precisely the input this map takes. is the pathname, which is precisely the input this map takes.
*/ */
/** The routes that must be able to answer, per workspace. */ /** Every platform page must be able to answer. */
const MUST_HAVE_AN_AGENT = [ const MUST_HAVE_AN_AGENT = ['/nearle/stores', '/nearle/onboard/tenant', '/nearle/catalogue'];
'/admin/console',
'/admin/sales',
'/admin/inventory',
'/store/console',
'/store/sales',
];
test('every workspace that can answer, does', () => { test('every workspace that can answer, does', () => {
for (const pathname of MUST_HAVE_AN_AGENT) { for (const pathname of MUST_HAVE_AN_AGENT) {
@@ -33,15 +27,31 @@ test('every workspace that can answer, does', () => {
} }
}); });
test('the two console pages answer with the same agent', () => { test('every platform page answers with the platform agent', () => {
// `/admin/console` and `/store/console` are the same page at two scopes. One // One agent here, not three. A platform account carries no tenant, so the
// of them carrying an agent and the other not is the exact shape of the bug, // eight tools that read a shop's data refuse it — `platform` holds `help`
// and it is invisible in the rendered output. // and nothing else. A page pointed at `console` or `orders` would accept a
const merchant = matchAssistantRoute('/admin/console').context; // question and answer "pick a shop first" to it.
const branch = matchAssistantRoute('/store/console').context; for (const pathname of MUST_HAVE_AN_AGENT) {
assert.equal(
matchAssistantRoute(pathname).context.agent,
'platform',
`${pathname} names an agent this console cannot use`,
);
}
});
assert.equal(branch.agent, merchant.agent); test('the merchant workspaces are not routes here', () => {
assert.equal(matchAssistantRoute('/store/sales').context.agent, 'orders'); // They moved out with the split. An entry left behind could never match —
// these are not paths in this application — and would only ship merchant
// copy to Nearle staff.
for (const pathname of ['/admin/console', '/admin/sales', '/store/console', '/store/sales']) {
assert.equal(
matchAssistantRoute(pathname).key,
undefined,
`${pathname} is still in this console's route map`,
);
}
}); });
test('a page falls through to the fallback rather than to nothing', () => { test('a page falls through to the fallback rather than to nothing', () => {
@@ -55,10 +65,10 @@ test('a page falls through to the fallback rather than to nothing', () => {
}); });
test('a deeper path still finds its page', () => { test('a deeper path still finds its page', () => {
// `/admin/sales/4412` is still the Sales panel. Exact matching would drop the // `/nearle/stores/1147` is still the Stores panel. Exact matching would drop
// agent the moment anybody opened a row. // the agent the moment anybody opened a tenant.
assert.equal(matchAssistantRoute('/admin/sales/4412').context.agent, 'orders'); assert.equal(matchAssistantRoute('/nearle/stores/1147').context.agent, 'platform');
assert.equal(matchAssistantRoute('/store/console?branch=3').context.agent, 'console'); assert.equal(matchAssistantRoute('/nearle/catalogue?brand=x').context.agent, 'platform');
}); });
test('no key shadows another', () => { test('no key shadows another', () => {

View File

@@ -95,158 +95,12 @@ export const CONTEXT: Record<
], ],
}, },
/* ── Store Admin ──────────────────────────────────────────────────────── */ /* The merchant workspaces were here — `/admin/*` and `/store/*`, with the
console, orders and inventory agents. They moved out with the split: those
paths are not routes in this application, so the entries could never match
and only shipped merchant copy to Nearle staff. They live in
`nearle-console`. */
'/admin/console': {
agent: 'console',
page: 'Console',
title: 'Across your branches',
greeting: 'App sales, counter sales and imported bills are counted separately — they live in different ledgers.',
reading: 'Would cover every branch — revenue by channel, stock health, till status and what is waiting on you.',
prompts: [
'Which branch is underperforming?',
'Any tills not syncing?',
'What needs my approval?',
'Where is stock running out?',
],
},
'/admin/sales': {
agent: 'orders',
page: 'Sales',
title: 'Orders and deliveries',
greeting: 'An app order and a counter bill are both sales, but only one of them has a delivery.',
reading: 'Would cover orders, counter bills and delivery progress across your branches.',
prompts: [
'Why is the cancel rate high?',
'Which orders are stuck?',
'Compare online and counter sales',
'What is out for delivery?',
],
},
'/admin/inventory': {
agent: 'inventory',
page: 'Inventory',
title: 'Catalogue and stock',
greeting: 'A product in the store catalogue does not mean stock on the shelf — that takes an approved request.',
reading: 'Would cover your catalogue, per-branch stock levels and the stock requests waiting on you.',
prompts: [
'What is waiting for approval?',
'What is low or out of stock?',
'What has run out completely?',
'Which branch is waiting on most stock?',
],
},
'/admin/users': {
page: 'Users & access',
title: 'Who can sign in',
greeting: 'A till account and a console login are two different things — a cashier has no console access at all.',
reading: 'Would cover your back-office directory and the till accounts at each branch.',
prompts: [
'What is the difference between the two?',
'How do I add a cashier?',
'Why can I not delete someone?',
'Who is inactive?',
],
},
'/admin/reports': {
page: 'Reports',
title: 'Revenue, sales and stock',
greeting: 'Fast and slow movers are the two lists that change what you order next.',
reading: 'Would cover revenue by channel and branch, product performance and stock movement.',
prompts: [
'Which products are slow moving?',
'Compare my branches',
'Online versus counter revenue',
'What is my inventory worth?',
],
},
/* ── Store user ───────────────────────────────────────────────────────────
*
* One branch, fixed. The pages are largely the SAME components the merchant
* sees — `/store/console` and `/admin/console` are both `ConsolePage` — so
* the absence of these entries was invisible in the code and total in the
* product: a store-manager got the fallback on every route, and the fallback
* has no agent, so the composer was dead for that whole role no matter what
* the deployment had configured.
*
* It stayed hidden because every test signed in as one of the other two
* roles, and because `/admin/console` and `/store/console` look identical on
* screen down to the heading.
*
* The wording differs from the merchant's on purpose. "Which branch is
* underperforming?" is not a question a branch user can act on, and the
* answers are scoped to their one branch by the session regardless — so the
* chips say what that scope actually is rather than implying a choice.
*/
'/store/console': {
agent: 'console',
page: 'Console',
title: 'Your branch today',
greeting: 'App sales, counter sales and imported bills are counted separately — they live in different ledgers.',
reading: 'Would cover this branch — sales by channel, stock health, till status and what is waiting.',
prompts: [
'Any tills not syncing?',
'Where is stock running out?',
'What needs attention?',
'How is today going?',
],
},
'/store/sales': {
agent: 'orders',
page: 'Sales',
title: 'Orders and deliveries',
greeting: 'An app order and a counter bill are both sales, but only one of them has a delivery.',
reading: 'Would cover this branch — orders, counter bills and delivery progress.',
prompts: [
'Which orders are stuck?',
'What is out for delivery?',
'Compare online and counter sales',
'Why is the cancel rate high?',
],
},
'/store/products': {
// No agent, and not an oversight. The `inventory` agent carries
// `approve_stock_request`, and whether a branch user may approve the stock
// their own branch asked for is a question about who is allowed to spend,
// not about which page this is. Until that is settled the page keeps the
// prompts and loses the composer, which is the honest of the two.
page: 'Products',
title: 'Catalogue and stock',
greeting: 'A product in the catalogue does not mean stock on the shelf — that takes an approved request.',
reading: 'Would cover this branch — what is on the shelf and what has been asked for.',
prompts: [
'What is low or out of stock?',
'What has run out completely?',
'How do I request stock?',
'Why is my request still pending?',
],
},
'/store/reports': {
page: 'Reports',
title: 'Revenue, sales and stock',
greeting: 'Fast and slow movers are the two lists that change what you order next.',
reading: 'Would cover this branch — revenue by channel, product performance and stock movement.',
prompts: [
'Which products are slow moving?',
'Online versus counter revenue',
'What sold best this week?',
'What is my stock worth?',
],
},
'/store/staff': {
page: 'Staff',
title: 'Who can sign in',
greeting: 'A till account and a console login are two different things — a cashier has no console access at all.',
reading: 'Would cover the people at this branch and the till accounts they sign in with.',
prompts: [
'What is the difference between the two?',
'How do I add a cashier?',
'Why can I not delete someone?',
'Who is inactive?',
],
},
}; };
const FALLBACK: (typeof CONTEXT)[string] = { const FALLBACK: (typeof CONTEXT)[string] = {