@entifix/next-shell 0.1.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/back-office/account-menu.d.ts +29 -0
- package/dist/lib/back-office/account-menu.d.ts.map +1 -0
- package/dist/lib/back-office/account-menu.js +70 -0
- package/dist/lib/back-office/account-menu.js.map +1 -0
- package/dist/lib/back-office/back-office-shell.d.ts +50 -0
- package/dist/lib/back-office/back-office-shell.d.ts.map +1 -0
- package/dist/lib/back-office/back-office-shell.js +128 -0
- package/dist/lib/back-office/back-office-shell.js.map +1 -0
- package/dist/lib/back-office/breadcrumbs.d.ts +16 -0
- package/dist/lib/back-office/breadcrumbs.d.ts.map +1 -0
- package/dist/lib/back-office/breadcrumbs.js +53 -0
- package/dist/lib/back-office/breadcrumbs.js.map +1 -0
- package/dist/lib/back-office/group-by-screen-type.d.ts +21 -0
- package/dist/lib/back-office/group-by-screen-type.d.ts.map +1 -0
- package/dist/lib/back-office/group-by-screen-type.js +39 -0
- package/dist/lib/back-office/group-by-screen-type.js.map +1 -0
- package/dist/lib/back-office/index.d.ts +6 -0
- package/dist/lib/back-office/index.d.ts.map +1 -0
- package/dist/lib/back-office/index.js +7 -0
- package/dist/lib/back-office/index.js.map +1 -0
- package/dist/lib/back-office/nav.d.ts +39 -0
- package/dist/lib/back-office/nav.d.ts.map +1 -0
- package/dist/lib/back-office/nav.js +3 -0
- package/dist/lib/back-office/nav.js.map +1 -0
- package/dist/lib/back-office/sidebar-nav.d.ts +26 -0
- package/dist/lib/back-office/sidebar-nav.d.ts.map +1 -0
- package/dist/lib/back-office/sidebar-nav.js +181 -0
- package/dist/lib/back-office/sidebar-nav.js.map +1 -0
- package/dist/lib/command-palette/command-palette-host.d.ts +41 -0
- package/dist/lib/command-palette/command-palette-host.d.ts.map +1 -0
- package/dist/lib/command-palette/command-palette-host.js +196 -0
- package/dist/lib/command-palette/command-palette-host.js.map +1 -0
- package/dist/lib/command-palette/index.d.ts +9 -0
- package/dist/lib/command-palette/index.d.ts.map +1 -0
- package/dist/lib/command-palette/index.js +10 -0
- package/dist/lib/command-palette/index.js.map +1 -0
- package/dist/lib/command-palette/palette-command.d.ts +32 -0
- package/dist/lib/command-palette/palette-command.d.ts.map +1 -0
- package/dist/lib/command-palette/palette-command.js +19 -0
- package/dist/lib/command-palette/palette-command.js.map +1 -0
- package/dist/lib/command-palette/use-command-recency.d.ts +28 -0
- package/dist/lib/command-palette/use-command-recency.d.ts.map +1 -0
- package/dist/lib/command-palette/use-command-recency.js +67 -0
- package/dist/lib/command-palette/use-command-recency.js.map +1 -0
- package/dist/lib/command-palette/use-command-source.d.ts +17 -0
- package/dist/lib/command-palette/use-command-source.d.ts.map +1 -0
- package/dist/lib/command-palette/use-command-source.js +70 -0
- package/dist/lib/command-palette/use-command-source.js.map +1 -0
- package/dist/lib/command-palette/use-nav-source.d.ts +21 -0
- package/dist/lib/command-palette/use-nav-source.d.ts.map +1 -0
- package/dist/lib/command-palette/use-nav-source.js +78 -0
- package/dist/lib/command-palette/use-nav-source.js.map +1 -0
- package/dist/lib/command-palette/use-records-source.d.ts +23 -0
- package/dist/lib/command-palette/use-records-source.d.ts.map +1 -0
- package/dist/lib/command-palette/use-records-source.js +199 -0
- package/dist/lib/command-palette/use-records-source.js.map +1 -0
- package/dist/lib/command-palette/use-tabs-source.d.ts +20 -0
- package/dist/lib/command-palette/use-tabs-source.d.ts.map +1 -0
- package/dist/lib/command-palette/use-tabs-source.js +52 -0
- package/dist/lib/command-palette/use-tabs-source.js.map +1 -0
- package/dist/lib/command-palette/use-use-cases-source.d.ts +22 -0
- package/dist/lib/command-palette/use-use-cases-source.d.ts.map +1 -0
- package/dist/lib/command-palette/use-use-cases-source.js +93 -0
- package/dist/lib/command-palette/use-use-cases-source.js.map +1 -0
- package/dist/lib/config/config-route.d.ts +51 -0
- package/dist/lib/config/config-route.d.ts.map +1 -0
- package/dist/lib/config/config-route.js +65 -0
- package/dist/lib/config/config-route.js.map +1 -0
- package/dist/lib/config/service-proxy-route.d.ts +28 -0
- package/dist/lib/config/service-proxy-route.d.ts.map +1 -0
- package/dist/lib/config/service-proxy-route.js +125 -0
- package/dist/lib/config/service-proxy-route.js.map +1 -0
- package/dist/lib/config/service-token.d.ts +5 -0
- package/dist/lib/config/service-token.d.ts.map +1 -0
- package/dist/lib/config/service-token.js +18 -0
- package/dist/lib/config/service-token.js.map +1 -0
- package/dist/lib/crud/entity-crud-form.d.ts +49 -0
- package/dist/lib/crud/entity-crud-form.d.ts.map +1 -0
- package/dist/lib/crud/entity-crud-form.js +133 -0
- package/dist/lib/crud/entity-crud-form.js.map +1 -0
- package/dist/lib/crud/hand-off-write.d.ts +35 -0
- package/dist/lib/crud/hand-off-write.d.ts.map +1 -0
- package/dist/lib/crud/hand-off-write.js +27 -0
- package/dist/lib/crud/hand-off-write.js.map +1 -0
- package/dist/lib/crud/index.d.ts +9 -0
- package/dist/lib/crud/index.d.ts.map +1 -0
- package/dist/lib/crud/index.js +10 -0
- package/dist/lib/crud/index.js.map +1 -0
- package/dist/lib/crud/make-entity-crud.d.ts +28 -0
- package/dist/lib/crud/make-entity-crud.d.ts.map +1 -0
- package/dist/lib/crud/make-entity-crud.js +318 -0
- package/dist/lib/crud/make-entity-crud.js.map +1 -0
- package/dist/lib/crud/make-entity-crud.types.d.ts +237 -0
- package/dist/lib/crud/make-entity-crud.types.d.ts.map +1 -0
- package/dist/lib/crud/make-entity-crud.types.js +6 -0
- package/dist/lib/crud/make-entity-crud.types.js.map +1 -0
- package/dist/lib/crud/pending-notice.d.ts +31 -0
- package/dist/lib/crud/pending-notice.d.ts.map +1 -0
- package/dist/lib/crud/pending-notice.js +72 -0
- package/dist/lib/crud/pending-notice.js.map +1 -0
- package/dist/lib/crud/slug.d.ts +13 -0
- package/dist/lib/crud/slug.d.ts.map +1 -0
- package/dist/lib/crud/slug.js +13 -0
- package/dist/lib/crud/slug.js.map +1 -0
- package/dist/lib/crud/use-entity-affordances.d.ts +20 -0
- package/dist/lib/crud/use-entity-affordances.d.ts.map +1 -0
- package/dist/lib/crud/use-entity-affordances.js +22 -0
- package/dist/lib/crud/use-entity-affordances.js.map +1 -0
- package/dist/lib/crud/use-entity-bulk.d.ts +43 -0
- package/dist/lib/crud/use-entity-bulk.d.ts.map +1 -0
- package/dist/lib/crud/use-entity-bulk.js +106 -0
- package/dist/lib/crud/use-entity-bulk.js.map +1 -0
- package/dist/lib/crud/use-entity-link-sources.d.ts +25 -0
- package/dist/lib/crud/use-entity-link-sources.d.ts.map +1 -0
- package/dist/lib/crud/use-entity-link-sources.js +37 -0
- package/dist/lib/crud/use-entity-link-sources.js.map +1 -0
- package/dist/lib/health/health-routes.d.ts +37 -0
- package/dist/lib/health/health-routes.d.ts.map +1 -0
- package/dist/lib/health/health-routes.js +90 -0
- package/dist/lib/health/health-routes.js.map +1 -0
- package/dist/lib/i18n/catalog/en.d.ts +4 -0
- package/dist/lib/i18n/catalog/en.d.ts.map +1 -0
- package/dist/lib/i18n/catalog/en.js +347 -0
- package/dist/lib/i18n/catalog/en.js.map +1 -0
- package/dist/lib/i18n/catalog/es.d.ts +397 -0
- package/dist/lib/i18n/catalog/es.d.ts.map +1 -0
- package/dist/lib/i18n/catalog/es.js +407 -0
- package/dist/lib/i18n/catalog/es.js.map +1 -0
- package/dist/lib/i18n/catalog/index.d.ts +671 -0
- package/dist/lib/i18n/catalog/index.d.ts.map +1 -0
- package/dist/lib/i18n/catalog/index.js +28 -0
- package/dist/lib/i18n/catalog/index.js.map +1 -0
- package/dist/lib/i18n/index.d.ts +2 -0
- package/dist/lib/i18n/index.d.ts.map +1 -0
- package/dist/lib/i18n/index.js +3 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/locale-link.d.ts +23 -0
- package/dist/lib/i18n/locale-link.d.ts.map +1 -0
- package/dist/lib/i18n/locale-link.js +43 -0
- package/dist/lib/i18n/locale-link.js.map +1 -0
- package/dist/lib/i18n/spec-catalog.d.ts +2 -0
- package/dist/lib/i18n/spec-catalog.d.ts.map +1 -0
- package/dist/lib/i18n/spec-catalog.js +84 -0
- package/dist/lib/i18n/spec-catalog.js.map +1 -0
- package/dist/lib/search/record-search-route.d.ts +44 -0
- package/dist/lib/search/record-search-route.d.ts.map +1 -0
- package/dist/lib/search/record-search-route.js +158 -0
- package/dist/lib/search/record-search-route.js.map +1 -0
- package/dist/lib/search/record-search-source.d.ts +59 -0
- package/dist/lib/search/record-search-source.d.ts.map +1 -0
- package/dist/lib/search/record-search-source.js +118 -0
- package/dist/lib/search/record-search-source.js.map +1 -0
- package/dist/lib/search/record-search.types.d.ts +85 -0
- package/dist/lib/search/record-search.types.d.ts.map +1 -0
- package/dist/lib/search/record-search.types.js +29 -0
- package/dist/lib/search/record-search.types.js.map +1 -0
- package/dist/lib/search/search-records.d.ts +25 -0
- package/dist/lib/search/search-records.d.ts.map +1 -0
- package/dist/lib/search/search-records.js +36 -0
- package/dist/lib/search/search-records.js.map +1 -0
- package/dist/lib/session/account-links.d.ts +46 -0
- package/dist/lib/session/account-links.d.ts.map +1 -0
- package/dist/lib/session/account-links.js +41 -0
- package/dist/lib/session/account-links.js.map +1 -0
- package/dist/lib/session/bearer.d.ts +25 -0
- package/dist/lib/session/bearer.d.ts.map +1 -0
- package/dist/lib/session/bearer.js +31 -0
- package/dist/lib/session/bearer.js.map +1 -0
- package/dist/lib/session/cookies.d.ts +14 -0
- package/dist/lib/session/cookies.d.ts.map +1 -0
- package/dist/lib/session/cookies.js +33 -0
- package/dist/lib/session/cookies.js.map +1 -0
- package/dist/lib/session/device.d.ts +31 -0
- package/dist/lib/session/device.d.ts.map +1 -0
- package/dist/lib/session/device.js +80 -0
- package/dist/lib/session/device.js.map +1 -0
- package/dist/lib/session/index.d.ts +3 -0
- package/dist/lib/session/index.d.ts.map +1 -0
- package/dist/lib/session/index.js +12 -0
- package/dist/lib/session/index.js.map +1 -0
- package/dist/lib/session/refresh-route.d.ts +19 -0
- package/dist/lib/session/refresh-route.d.ts.map +1 -0
- package/dist/lib/session/refresh-route.js +85 -0
- package/dist/lib/session/refresh-route.js.map +1 -0
- package/dist/lib/session/session-keepalive.d.ts +52 -0
- package/dist/lib/session/session-keepalive.d.ts.map +1 -0
- package/dist/lib/session/session-keepalive.js +72 -0
- package/dist/lib/session/session-keepalive.js.map +1 -0
- package/dist/lib/session/use-session-refresh.d.ts +43 -0
- package/dist/lib/session/use-session-refresh.d.ts.map +1 -0
- package/dist/lib/session/use-session-refresh.js +120 -0
- package/dist/lib/session/use-session-refresh.js.map +1 -0
- package/dist/lib/wizard/index.d.ts +4 -0
- package/dist/lib/wizard/index.d.ts.map +1 -0
- package/dist/lib/wizard/index.js +5 -0
- package/dist/lib/wizard/index.js.map +1 -0
- package/dist/lib/wizard/use-wizard-draft.d.ts +22 -0
- package/dist/lib/wizard/use-wizard-draft.d.ts.map +1 -0
- package/dist/lib/wizard/use-wizard-draft.js +39 -0
- package/dist/lib/wizard/use-wizard-draft.js.map +1 -0
- package/dist/lib/wizard/use-wizard-step-param.d.ts +60 -0
- package/dist/lib/wizard/use-wizard-step-param.d.ts.map +1 -0
- package/dist/lib/wizard/use-wizard-step-param.js +94 -0
- package/dist/lib/wizard/use-wizard-step-param.js.map +1 -0
- package/dist/lib/wizard/wizard-tab-kind.d.ts +31 -0
- package/dist/lib/wizard/wizard-tab-kind.d.ts.map +1 -0
- package/dist/lib/wizard/wizard-tab-kind.js +33 -0
- package/dist/lib/wizard/wizard-tab-kind.js.map +1 -0
- package/dist/lib/workspace/drafts-state.d.ts +88 -0
- package/dist/lib/workspace/drafts-state.d.ts.map +1 -0
- package/dist/lib/workspace/drafts-state.js +128 -0
- package/dist/lib/workspace/drafts-state.js.map +1 -0
- package/dist/lib/workspace/entity-nav.d.ts +26 -0
- package/dist/lib/workspace/entity-nav.d.ts.map +1 -0
- package/dist/lib/workspace/entity-nav.js +56 -0
- package/dist/lib/workspace/entity-nav.js.map +1 -0
- package/dist/lib/workspace/entity-tab-kind.d.ts +51 -0
- package/dist/lib/workspace/entity-tab-kind.d.ts.map +1 -0
- package/dist/lib/workspace/entity-tab-kind.js +47 -0
- package/dist/lib/workspace/entity-tab-kind.js.map +1 -0
- package/dist/lib/workspace/idb-state-storage.d.ts +9 -0
- package/dist/lib/workspace/idb-state-storage.d.ts.map +1 -0
- package/dist/lib/workspace/idb-state-storage.js +32 -0
- package/dist/lib/workspace/idb-state-storage.js.map +1 -0
- package/dist/lib/workspace/index.d.ts +13 -0
- package/dist/lib/workspace/index.d.ts.map +1 -0
- package/dist/lib/workspace/index.js +14 -0
- package/dist/lib/workspace/index.js.map +1 -0
- package/dist/lib/workspace/pending-state.d.ts +84 -0
- package/dist/lib/workspace/pending-state.d.ts.map +1 -0
- package/dist/lib/workspace/pending-state.js +155 -0
- package/dist/lib/workspace/pending-state.js.map +1 -0
- package/dist/lib/workspace/pending-transactions.d.ts +44 -0
- package/dist/lib/workspace/pending-transactions.d.ts.map +1 -0
- package/dist/lib/workspace/pending-transactions.js +114 -0
- package/dist/lib/workspace/pending-transactions.js.map +1 -0
- package/dist/lib/workspace/tab-kind.d.ts +48 -0
- package/dist/lib/workspace/tab-kind.d.ts.map +1 -0
- package/dist/lib/workspace/tab-kind.js +47 -0
- package/dist/lib/workspace/tab-kind.js.map +1 -0
- package/dist/lib/workspace/tab-state.d.ts +36 -0
- package/dist/lib/workspace/tab-state.d.ts.map +1 -0
- package/dist/lib/workspace/tab-state.js +81 -0
- package/dist/lib/workspace/tab-state.js.map +1 -0
- package/dist/lib/workspace/tabs-state.d.ts +46 -0
- package/dist/lib/workspace/tabs-state.d.ts.map +1 -0
- package/dist/lib/workspace/tabs-state.js +56 -0
- package/dist/lib/workspace/tabs-state.js.map +1 -0
- package/dist/lib/workspace/use-entity-draft.d.ts +25 -0
- package/dist/lib/workspace/use-entity-draft.d.ts.map +1 -0
- package/dist/lib/workspace/use-entity-draft.js +38 -0
- package/dist/lib/workspace/use-entity-draft.js.map +1 -0
- package/dist/lib/workspace/workspace-scope.d.ts +51 -0
- package/dist/lib/workspace/workspace-scope.d.ts.map +1 -0
- package/dist/lib/workspace/workspace-scope.js +42 -0
- package/dist/lib/workspace/workspace-scope.js.map +1 -0
- package/dist/lib/workspace/workspace-shell.d.ts +33 -0
- package/dist/lib/workspace/workspace-shell.d.ts.map +1 -0
- package/dist/lib/workspace/workspace-shell.js +206 -0
- package/dist/lib/workspace/workspace-shell.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +41 -0
- package/dist/server.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"palette-command.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/palette-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,0DAA0D;AAC1D,eAAO,MAAM,yBAAyB,MAAM,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deterministic command, with its copy already resolved.
|
|
3
|
+
*
|
|
4
|
+
* The wire shape between a **server** layout — which is where `visibleNav`'s
|
|
5
|
+
* principal and the translate function live — and the client palette. So it is
|
|
6
|
+
* plain serializable data and nothing else: a `GuardedCommand` carries catalog
|
|
7
|
+
* keys and a `Permission`, neither of which a client component should have to
|
|
8
|
+
* deal with, and a function would not survive the crossing at all.
|
|
9
|
+
*/ /**
|
|
10
|
+
* How many characters before records are queried at all.
|
|
11
|
+
*
|
|
12
|
+
* The same floor `/api/search` enforces, restated here so the palette can *say*
|
|
13
|
+
* why a record group is absent instead of silently showing nothing. Below it the
|
|
14
|
+
* route answers empty groups, which would otherwise read as "no matches" for a
|
|
15
|
+
* search that never ran.
|
|
16
|
+
*/ export const RECORD_SEARCH_MIN_TERM = 2;
|
|
17
|
+
/** How long after the last keystroke the fan-out runs. */ export const RECORD_SEARCH_DEBOUNCE_MS = 250;
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=palette-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/command-palette/palette-command.ts"],"sourcesContent":["/**\n * A deterministic command, with its copy already resolved.\n *\n * The wire shape between a **server** layout — which is where `visibleNav`'s\n * principal and the translate function live — and the client palette. So it is\n * plain serializable data and nothing else: a `GuardedCommand` carries catalog\n * keys and a `Permission`, neither of which a client component should have to\n * deal with, and a function would not survive the crossing at all.\n */\nexport interface PaletteCommand {\n /** Stable across renders and hosts. Also the recency key. */\n readonly key: string;\n readonly label: string;\n /** Already split from the catalog's comma list by the host. */\n readonly keywords: readonly string[];\n /** Locale-free; the palette prefixes it. */\n readonly href: string;\n /** The page this command sits on. Omitted means the root. */\n readonly page?: string;\n}\n\n/**\n * How many characters before records are queried at all.\n *\n * The same floor `/api/search` enforces, restated here so the palette can *say*\n * why a record group is absent instead of silently showing nothing. Below it the\n * route answers empty groups, which would otherwise read as \"no matches\" for a\n * search that never ran.\n */\nexport const RECORD_SEARCH_MIN_TERM = 2;\n\n/** How long after the last keystroke the fan-out runs. */\nexport const RECORD_SEARCH_DEBOUNCE_MS = 250;\n"],"names":["RECORD_SEARCH_MIN_TERM","RECORD_SEARCH_DEBOUNCE_MS"],"mappings":"AAAA;;;;;;;;CAQC,GAaD;;;;;;;CAOC,GACD,OAAO,MAAMA,yBAAyB,EAAE;AAExC,wDAAwD,GACxD,OAAO,MAAMC,4BAA4B,IAAI"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CommandGroup } from '@entifix/core';
|
|
2
|
+
export interface CommandRecency {
|
|
3
|
+
/** Reorder a group's options, most recently used first. */
|
|
4
|
+
readonly rank: (group: CommandGroup) => CommandGroup;
|
|
5
|
+
/** Remember that this command was just run. */
|
|
6
|
+
readonly remember: (id: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Which commands this person reaches for, and in what order to offer them.
|
|
10
|
+
*
|
|
11
|
+
* **Recency reorders within a group and never across groups.** Group order is
|
|
12
|
+
* the ranking (ADR 0040), and it is fixed precisely so an operator learns where
|
|
13
|
+
* a kind of result lands; letting use move a whole group would take that away
|
|
14
|
+
* for a convenience nobody asked for. Inside a group there is no such contract,
|
|
15
|
+
* so the last thing you ran comes first.
|
|
16
|
+
*
|
|
17
|
+
* Stored as **one keyed record**, not a key per command — the same reasoning
|
|
18
|
+
* `back-office:nav-collapsed-groups` records: `useUiPreference` resolves after
|
|
19
|
+
* mount, so N keys are N first-paint transitions while one read settles the
|
|
20
|
+
* whole palette at once.
|
|
21
|
+
*
|
|
22
|
+
* A record group is deliberately left alone: its options are records, whose ids
|
|
23
|
+
* are primary keys, so remembering them would build a list of which records this
|
|
24
|
+
* person opened inside a UI preference. Only commands and destinations are
|
|
25
|
+
* ranked.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useCommandRecency(): CommandRecency;
|
|
28
|
+
//# sourceMappingURL=use-command-recency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-command-recency.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-command-recency.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAiBlD,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,YAAY,CAAC;IACrD,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CA8BlD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
3
|
+
import { useUiPreference } from '@entifix/react-controls';
|
|
4
|
+
import { useCallback, useMemo } from 'react';
|
|
5
|
+
/** `namespace:` is prepended by the store → `entifix-ui:back-office:command-recency`. */ const RECENCY_PREF_KEY = 'back-office:command-recency';
|
|
6
|
+
/**
|
|
7
|
+
* How many commands are remembered.
|
|
8
|
+
*
|
|
9
|
+
* A cap rather than an unbounded map: the store writes the whole record on every
|
|
10
|
+
* run, and a record that only ever grows is a preference that gets slower for
|
|
11
|
+
* the rest of the person's account life. Twenty is far more than the number of
|
|
12
|
+
* commands anyone reaches for.
|
|
13
|
+
*/ const RECENCY_LIMIT = 20;
|
|
14
|
+
/**
|
|
15
|
+
* Which commands this person reaches for, and in what order to offer them.
|
|
16
|
+
*
|
|
17
|
+
* **Recency reorders within a group and never across groups.** Group order is
|
|
18
|
+
* the ranking (ADR 0040), and it is fixed precisely so an operator learns where
|
|
19
|
+
* a kind of result lands; letting use move a whole group would take that away
|
|
20
|
+
* for a convenience nobody asked for. Inside a group there is no such contract,
|
|
21
|
+
* so the last thing you ran comes first.
|
|
22
|
+
*
|
|
23
|
+
* Stored as **one keyed record**, not a key per command — the same reasoning
|
|
24
|
+
* `back-office:nav-collapsed-groups` records: `useUiPreference` resolves after
|
|
25
|
+
* mount, so N keys are N first-paint transitions while one read settles the
|
|
26
|
+
* whole palette at once.
|
|
27
|
+
*
|
|
28
|
+
* A record group is deliberately left alone: its options are records, whose ids
|
|
29
|
+
* are primary keys, so remembering them would build a list of which records this
|
|
30
|
+
* person opened inside a UI preference. Only commands and destinations are
|
|
31
|
+
* ranked.
|
|
32
|
+
*/ export function useCommandRecency() {
|
|
33
|
+
const { value, setValue } = useUiPreference(RECENCY_PREF_KEY, {});
|
|
34
|
+
const rank = useCallback((group)=>{
|
|
35
|
+
if (group.key.startsWith('record:')) return group;
|
|
36
|
+
const options = [
|
|
37
|
+
...group.options
|
|
38
|
+
].sort((left, right)=>{
|
|
39
|
+
var _value_right_id, _value_left_id;
|
|
40
|
+
return ((_value_right_id = value[right.id]) != null ? _value_right_id : 0) - ((_value_left_id = value[left.id]) != null ? _value_left_id : 0);
|
|
41
|
+
});
|
|
42
|
+
return _extends({}, group, {
|
|
43
|
+
options
|
|
44
|
+
});
|
|
45
|
+
}, [
|
|
46
|
+
value
|
|
47
|
+
]);
|
|
48
|
+
const remember = useCallback((id)=>{
|
|
49
|
+
const next = _extends({}, value, {
|
|
50
|
+
[id]: Date.now()
|
|
51
|
+
});
|
|
52
|
+
const kept = Object.entries(next).sort(([, left], [, right])=>right - left).slice(0, RECENCY_LIMIT);
|
|
53
|
+
setValue(Object.fromEntries(kept));
|
|
54
|
+
}, [
|
|
55
|
+
value,
|
|
56
|
+
setValue
|
|
57
|
+
]);
|
|
58
|
+
return useMemo(()=>({
|
|
59
|
+
rank,
|
|
60
|
+
remember
|
|
61
|
+
}), [
|
|
62
|
+
rank,
|
|
63
|
+
remember
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=use-command-recency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/command-palette/use-command-recency.ts"],"sourcesContent":["'use client';\n\nimport type { CommandGroup } from '@entifix/core';\nimport { useUiPreference } from '@entifix/react-controls';\nimport { useCallback, useMemo } from 'react';\n\n/** `namespace:` is prepended by the store → `entifix-ui:back-office:command-recency`. */\nconst RECENCY_PREF_KEY = 'back-office:command-recency';\n\n/**\n * How many commands are remembered.\n *\n * A cap rather than an unbounded map: the store writes the whole record on every\n * run, and a record that only ever grows is a preference that gets slower for\n * the rest of the person's account life. Twenty is far more than the number of\n * commands anyone reaches for.\n */\nconst RECENCY_LIMIT = 20;\n\nexport interface CommandRecency {\n /** Reorder a group's options, most recently used first. */\n readonly rank: (group: CommandGroup) => CommandGroup;\n /** Remember that this command was just run. */\n readonly remember: (id: string) => void;\n}\n\n/**\n * Which commands this person reaches for, and in what order to offer them.\n *\n * **Recency reorders within a group and never across groups.** Group order is\n * the ranking (ADR 0040), and it is fixed precisely so an operator learns where\n * a kind of result lands; letting use move a whole group would take that away\n * for a convenience nobody asked for. Inside a group there is no such contract,\n * so the last thing you ran comes first.\n *\n * Stored as **one keyed record**, not a key per command — the same reasoning\n * `back-office:nav-collapsed-groups` records: `useUiPreference` resolves after\n * mount, so N keys are N first-paint transitions while one read settles the\n * whole palette at once.\n *\n * A record group is deliberately left alone: its options are records, whose ids\n * are primary keys, so remembering them would build a list of which records this\n * person opened inside a UI preference. Only commands and destinations are\n * ranked.\n */\nexport function useCommandRecency(): CommandRecency {\n const { value, setValue } = useUiPreference<Record<string, number>>(\n RECENCY_PREF_KEY,\n {},\n );\n\n const rank = useCallback(\n (group: CommandGroup): CommandGroup => {\n if (group.key.startsWith('record:')) return group;\n\n const options = [...group.options].sort(\n (left, right) => (value[right.id] ?? 0) - (value[left.id] ?? 0),\n );\n return { ...group, options };\n },\n [value],\n );\n\n const remember = useCallback(\n (id: string) => {\n const next = { ...value, [id]: Date.now() };\n const kept = Object.entries(next)\n .sort(([, left], [, right]) => right - left)\n .slice(0, RECENCY_LIMIT);\n setValue(Object.fromEntries(kept));\n },\n [value, setValue],\n );\n\n return useMemo(() => ({ rank, remember }), [rank, remember]);\n}\n"],"names":["useUiPreference","useCallback","useMemo","RECENCY_PREF_KEY","RECENCY_LIMIT","useCommandRecency","value","setValue","rank","group","key","startsWith","options","sort","left","right","id","remember","next","Date","now","kept","Object","entries","slice","fromEntries"],"mappings":"AAAA;;AAGA,SAASA,eAAe,QAAQ,0BAA0B;AAC1D,SAASC,WAAW,EAAEC,OAAO,QAAQ,QAAQ;AAE7C,uFAAuF,GACvF,MAAMC,mBAAmB;AAEzB;;;;;;;CAOC,GACD,MAAMC,gBAAgB;AAStB;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC;IACd,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGP,gBAC1BG,kBACA,CAAC;IAGH,MAAMK,OAAOP,YACX,CAACQ;QACC,IAAIA,MAAMC,GAAG,CAACC,UAAU,CAAC,YAAY,OAAOF;QAE5C,MAAMG,UAAU;eAAIH,MAAMG,OAAO;SAAC,CAACC,IAAI,CACrC,CAACC,MAAMC;gBAAWT,iBAAyBA;mBAA1B,EAACA,kBAAAA,KAAK,CAACS,MAAMC,EAAE,CAAC,YAAfV,kBAAmB,OAAMA,iBAAAA,KAAK,CAACQ,KAAKE,EAAE,CAAC,YAAdV,iBAAkB;;QAE/D,OAAO,aAAKG;YAAOG;;IACrB,GACA;QAACN;KAAM;IAGT,MAAMW,WAAWhB,YACf,CAACe;QACC,MAAME,OAAO,aAAKZ;YAAO,CAACU,GAAG,EAAEG,KAAKC,GAAG;;QACvC,MAAMC,OAAOC,OAAOC,OAAO,CAACL,MACzBL,IAAI,CAAC,CAAC,GAAGC,KAAK,EAAE,GAAGC,MAAM,GAAKA,QAAQD,MACtCU,KAAK,CAAC,GAAGpB;QACZG,SAASe,OAAOG,WAAW,CAACJ;IAC9B,GACA;QAACf;QAAOC;KAAS;IAGnB,OAAOL,QAAQ,IAAO,CAAA;YAAEM;YAAMS;QAAS,CAAA,GAAI;QAACT;QAAMS;KAAS;AAC7D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CommandSource } from '@entifix/core';
|
|
2
|
+
import type { PaletteCommand } from './palette-command.js';
|
|
3
|
+
/**
|
|
4
|
+
* The deterministic commands — "Nuevo producto", "Nuevo usuario" — as one group.
|
|
5
|
+
*
|
|
6
|
+
* **This hook filters; the palette does not.** Every source in the palette
|
|
7
|
+
* arrives pre-filtered, because the record source is filtered by the service
|
|
8
|
+
* that answered it and a component that filtered again would have to know which
|
|
9
|
+
* groups were already narrowed.
|
|
10
|
+
*
|
|
11
|
+
* Commands declared on a sub-page do not appear at the root. What appears there
|
|
12
|
+
* instead is one entry that descends into the page, which is what makes the
|
|
13
|
+
* palette's first screen a short list of *kinds* of thing rather than one row
|
|
14
|
+
* per creatable entity competing with the destinations beside them.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useCommandSource(commands: readonly PaletteCommand[], term: string, page: string | undefined): CommandSource;
|
|
17
|
+
//# sourceMappingURL=use-command-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-command-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-command-source.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,aAAa,CAyCf"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { NEW_COMMAND_PAGE } from '@entifix/authz';
|
|
3
|
+
import { matchesCommand } from '@entifix/core';
|
|
4
|
+
import { useT } from '@entifix/react-controls';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { useLocaleHref } from '../i18n/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* The deterministic commands — "Nuevo producto", "Nuevo usuario" — as one group.
|
|
9
|
+
*
|
|
10
|
+
* **This hook filters; the palette does not.** Every source in the palette
|
|
11
|
+
* arrives pre-filtered, because the record source is filtered by the service
|
|
12
|
+
* that answered it and a component that filtered again would have to know which
|
|
13
|
+
* groups were already narrowed.
|
|
14
|
+
*
|
|
15
|
+
* Commands declared on a sub-page do not appear at the root. What appears there
|
|
16
|
+
* instead is one entry that descends into the page, which is what makes the
|
|
17
|
+
* palette's first screen a short list of *kinds* of thing rather than one row
|
|
18
|
+
* per creatable entity competing with the destinations beside them.
|
|
19
|
+
*/ export function useCommandSource(commands, term, page) {
|
|
20
|
+
const t = useT('shell');
|
|
21
|
+
const withLocale = useLocaleHref();
|
|
22
|
+
return useMemo(()=>{
|
|
23
|
+
const onThisPage = commands.filter((command)=>{
|
|
24
|
+
var _command_page;
|
|
25
|
+
return ((_command_page = command.page) != null ? _command_page : undefined) === page;
|
|
26
|
+
});
|
|
27
|
+
const options = onThisPage.map((command)=>({
|
|
28
|
+
id: command.key,
|
|
29
|
+
label: command.label,
|
|
30
|
+
keywords: command.keywords,
|
|
31
|
+
href: withLocale(command.href)
|
|
32
|
+
}));
|
|
33
|
+
// The root gains the entry that opens the create page — synthesized rather
|
|
34
|
+
// than declared, because no shell can know whether *another* shell also
|
|
35
|
+
// contributed a create command, and two shells each declaring the opener
|
|
36
|
+
// would render it twice.
|
|
37
|
+
if (page === undefined && commands.some((c)=>c.page === NEW_COMMAND_PAGE)) {
|
|
38
|
+
options.unshift({
|
|
39
|
+
id: 'palette:new',
|
|
40
|
+
label: t('commandPalette.newLabel'),
|
|
41
|
+
keywords: [
|
|
42
|
+
'new',
|
|
43
|
+
'create',
|
|
44
|
+
'nuevo',
|
|
45
|
+
'crear'
|
|
46
|
+
],
|
|
47
|
+
push: NEW_COMMAND_PAGE
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
key: 'commands',
|
|
52
|
+
groups: [
|
|
53
|
+
{
|
|
54
|
+
key: 'commands',
|
|
55
|
+
label: t('commandPalette.groups.commands'),
|
|
56
|
+
options: options.filter((option)=>matchesCommand(term, option)),
|
|
57
|
+
isLoading: false
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
};
|
|
61
|
+
}, [
|
|
62
|
+
commands,
|
|
63
|
+
page,
|
|
64
|
+
term,
|
|
65
|
+
t,
|
|
66
|
+
withLocale
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=use-command-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/command-palette/use-command-source.ts"],"sourcesContent":["'use client';\n\nimport { NEW_COMMAND_PAGE } from '@entifix/authz';\nimport {\n type CommandOption,\n type CommandSource,\n matchesCommand,\n} from '@entifix/core';\nimport { useT } from '@entifix/react-controls';\nimport { useMemo } from 'react';\n\nimport { useLocaleHref } from '../i18n/index.js';\nimport type { PaletteCommand } from './palette-command.js';\n\n/**\n * The deterministic commands — \"Nuevo producto\", \"Nuevo usuario\" — as one group.\n *\n * **This hook filters; the palette does not.** Every source in the palette\n * arrives pre-filtered, because the record source is filtered by the service\n * that answered it and a component that filtered again would have to know which\n * groups were already narrowed.\n *\n * Commands declared on a sub-page do not appear at the root. What appears there\n * instead is one entry that descends into the page, which is what makes the\n * palette's first screen a short list of *kinds* of thing rather than one row\n * per creatable entity competing with the destinations beside them.\n */\nexport function useCommandSource(\n commands: readonly PaletteCommand[],\n term: string,\n page: string | undefined,\n): CommandSource {\n const t = useT('shell');\n const withLocale = useLocaleHref();\n\n return useMemo(() => {\n const onThisPage = commands.filter(\n command => (command.page ?? undefined) === page,\n );\n\n const options: CommandOption[] = onThisPage.map(command => ({\n id: command.key,\n label: command.label,\n keywords: command.keywords,\n href: withLocale(command.href),\n }));\n\n // The root gains the entry that opens the create page — synthesized rather\n // than declared, because no shell can know whether *another* shell also\n // contributed a create command, and two shells each declaring the opener\n // would render it twice.\n if (page === undefined && commands.some(c => c.page === NEW_COMMAND_PAGE)) {\n options.unshift({\n id: 'palette:new',\n label: t('commandPalette.newLabel'),\n keywords: ['new', 'create', 'nuevo', 'crear'],\n push: NEW_COMMAND_PAGE,\n });\n }\n\n return {\n key: 'commands',\n groups: [\n {\n key: 'commands',\n label: t('commandPalette.groups.commands'),\n options: options.filter(option => matchesCommand(term, option)),\n isLoading: false,\n },\n ],\n } satisfies CommandSource;\n }, [commands, page, term, t, withLocale]);\n}\n"],"names":["NEW_COMMAND_PAGE","matchesCommand","useT","useMemo","useLocaleHref","useCommandSource","commands","term","page","t","withLocale","onThisPage","filter","command","undefined","options","map","id","key","label","keywords","href","some","c","unshift","push","groups","option","isLoading"],"mappings":"AAAA;AAEA,SAASA,gBAAgB,QAAQ,iBAAiB;AAClD,SAGEC,cAAc,QACT,gBAAgB;AACvB,SAASC,IAAI,QAAQ,0BAA0B;AAC/C,SAASC,OAAO,QAAQ,QAAQ;AAEhC,SAASC,aAAa,QAAQ,mBAAmB;AAGjD;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,iBACdC,QAAmC,EACnCC,IAAY,EACZC,IAAwB;IAExB,MAAMC,IAAIP,KAAK;IACf,MAAMQ,aAAaN;IAEnB,OAAOD,QAAQ;QACb,MAAMQ,aAAaL,SAASM,MAAM,CAChCC,CAAAA;gBAAYA;mBAAD,EAACA,gBAAAA,QAAQL,IAAI,YAAZK,gBAAgBC,eAAeN;;QAG7C,MAAMO,UAA2BJ,WAAWK,GAAG,CAACH,CAAAA,UAAY,CAAA;gBAC1DI,IAAIJ,QAAQK,GAAG;gBACfC,OAAON,QAAQM,KAAK;gBACpBC,UAAUP,QAAQO,QAAQ;gBAC1BC,MAAMX,WAAWG,QAAQQ,IAAI;YAC/B,CAAA;QAEA,2EAA2E;QAC3E,wEAAwE;QACxE,yEAAyE;QACzE,yBAAyB;QACzB,IAAIb,SAASM,aAAaR,SAASgB,IAAI,CAACC,CAAAA,IAAKA,EAAEf,IAAI,KAAKR,mBAAmB;YACzEe,QAAQS,OAAO,CAAC;gBACdP,IAAI;gBACJE,OAAOV,EAAE;gBACTW,UAAU;oBAAC;oBAAO;oBAAU;oBAAS;iBAAQ;gBAC7CK,MAAMzB;YACR;QACF;QAEA,OAAO;YACLkB,KAAK;YACLQ,QAAQ;gBACN;oBACER,KAAK;oBACLC,OAAOV,EAAE;oBACTM,SAASA,QAAQH,MAAM,CAACe,CAAAA,SAAU1B,eAAeM,MAAMoB;oBACvDC,WAAW;gBACb;aACD;QACH;IACF,GAAG;QAACtB;QAAUE;QAAMD;QAAME;QAAGC;KAAW;AAC1C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CommandSource } from '@entifix/core';
|
|
2
|
+
import type { NavSection } from '../back-office/nav.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every destination this caller may reach, as one group.
|
|
5
|
+
*
|
|
6
|
+
* The nav is already the answer to "what may this person see": `visibleNav`
|
|
7
|
+
* applies both of ADR 0037's ceilings server-side, and what reaches here is the
|
|
8
|
+
* resolved, filtered tree the sidebar renders. So the palette holds no second
|
|
9
|
+
* projection of navigation — the thing ADR 0037 deleted `GET /api/menu` for.
|
|
10
|
+
*
|
|
11
|
+
* A destination that can also be a workspace tab contributes a **second**
|
|
12
|
+
* option rather than a modifier on the first. The palette runs on Enter and has
|
|
13
|
+
* no place to hang "hold ⌥ to open as a tab", and a hidden modifier is exactly
|
|
14
|
+
* the undiscoverable affordance the sidebar's own workspace link was fixed for.
|
|
15
|
+
*
|
|
16
|
+
* The section's screen type is the option's hint, so Definiciones and
|
|
17
|
+
* Operaciones stay distinguishable in a flat list — the sidebar's top tier
|
|
18
|
+
* (ADR 0033) surviving the flattening rather than being dropped by it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useNavSource(sections: readonly NavSection[], term: string): CommandSource;
|
|
21
|
+
//# sourceMappingURL=use-nav-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-nav-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-nav-source.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AAIvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAOxD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,IAAI,EAAE,MAAM,GACX,aAAa,CAkDf"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
3
|
+
import { SCREEN_TYPE_LABEL_KEYS } from '@entifix/authz';
|
|
4
|
+
import { matchesCommand } from '@entifix/core';
|
|
5
|
+
import { useT, useTranslateKey } from '@entifix/react-controls';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { useLocaleHref } from '../i18n/index.js';
|
|
8
|
+
/** The workspace deep link a nav item's `workspace` address resolves to. */ const workspaceHref = (workspace)=>`/workspace?tab=${encodeURIComponent(workspace)}`;
|
|
9
|
+
/**
|
|
10
|
+
* Every destination this caller may reach, as one group.
|
|
11
|
+
*
|
|
12
|
+
* The nav is already the answer to "what may this person see": `visibleNav`
|
|
13
|
+
* applies both of ADR 0037's ceilings server-side, and what reaches here is the
|
|
14
|
+
* resolved, filtered tree the sidebar renders. So the palette holds no second
|
|
15
|
+
* projection of navigation — the thing ADR 0037 deleted `GET /api/menu` for.
|
|
16
|
+
*
|
|
17
|
+
* A destination that can also be a workspace tab contributes a **second**
|
|
18
|
+
* option rather than a modifier on the first. The palette runs on Enter and has
|
|
19
|
+
* no place to hang "hold ⌥ to open as a tab", and a hidden modifier is exactly
|
|
20
|
+
* the undiscoverable affordance the sidebar's own workspace link was fixed for.
|
|
21
|
+
*
|
|
22
|
+
* The section's screen type is the option's hint, so Definiciones and
|
|
23
|
+
* Operaciones stay distinguishable in a flat list — the sidebar's top tier
|
|
24
|
+
* (ADR 0033) surviving the flattening rather than being dropped by it.
|
|
25
|
+
*/ export function useNavSource(sections, term) {
|
|
26
|
+
const t = useT('shell');
|
|
27
|
+
const translateKey = useTranslateKey();
|
|
28
|
+
const withLocale = useLocaleHref();
|
|
29
|
+
return useMemo(()=>{
|
|
30
|
+
const options = [];
|
|
31
|
+
for (const section of sections){
|
|
32
|
+
// Only the screen type. Falling back to the section title here read as
|
|
33
|
+
// "Perfil · Cuenta · Cuenta" on the untyped account section, because the
|
|
34
|
+
// title is already the sublabel — one fact, printed twice.
|
|
35
|
+
const hint = section.type === undefined ? undefined : translateKey(SCREEN_TYPE_LABEL_KEYS[section.type]);
|
|
36
|
+
for (const item of section.items){
|
|
37
|
+
options.push(_extends({
|
|
38
|
+
id: `nav:${item.href}`,
|
|
39
|
+
label: item.label,
|
|
40
|
+
href: withLocale(item.href)
|
|
41
|
+
}, hint === undefined ? {} : {
|
|
42
|
+
hint
|
|
43
|
+
}, section.title === undefined ? {} : {
|
|
44
|
+
sublabel: section.title
|
|
45
|
+
}));
|
|
46
|
+
if (item.workspace !== undefined) {
|
|
47
|
+
options.push(_extends({
|
|
48
|
+
id: `nav:workspace:${item.workspace}`,
|
|
49
|
+
label: item.label,
|
|
50
|
+
href: withLocale(workspaceHref(item.workspace)),
|
|
51
|
+
hint: t('commandPalette.openInWorkspaceHint')
|
|
52
|
+
}, section.title === undefined ? {} : {
|
|
53
|
+
sublabel: section.title
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
key: 'navigation',
|
|
60
|
+
groups: [
|
|
61
|
+
{
|
|
62
|
+
key: 'navigation',
|
|
63
|
+
label: t('commandPalette.groups.navigation'),
|
|
64
|
+
options: options.filter((option)=>matchesCommand(term, option)),
|
|
65
|
+
isLoading: false
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}, [
|
|
70
|
+
sections,
|
|
71
|
+
term,
|
|
72
|
+
t,
|
|
73
|
+
translateKey,
|
|
74
|
+
withLocale
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=use-nav-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/command-palette/use-nav-source.ts"],"sourcesContent":["'use client';\n\nimport { SCREEN_TYPE_LABEL_KEYS } from '@entifix/authz';\nimport {\n type CommandOption,\n type CommandSource,\n matchesCommand,\n} from '@entifix/core';\nimport { useT, useTranslateKey } from '@entifix/react-controls';\nimport { useMemo } from 'react';\n\nimport type { NavSection } from '../back-office/nav.js';\nimport { useLocaleHref } from '../i18n/index.js';\n\n/** The workspace deep link a nav item's `workspace` address resolves to. */\nconst workspaceHref = (workspace: string) =>\n `/workspace?tab=${encodeURIComponent(workspace)}`;\n\n/**\n * Every destination this caller may reach, as one group.\n *\n * The nav is already the answer to \"what may this person see\": `visibleNav`\n * applies both of ADR 0037's ceilings server-side, and what reaches here is the\n * resolved, filtered tree the sidebar renders. So the palette holds no second\n * projection of navigation — the thing ADR 0037 deleted `GET /api/menu` for.\n *\n * A destination that can also be a workspace tab contributes a **second**\n * option rather than a modifier on the first. The palette runs on Enter and has\n * no place to hang \"hold ⌥ to open as a tab\", and a hidden modifier is exactly\n * the undiscoverable affordance the sidebar's own workspace link was fixed for.\n *\n * The section's screen type is the option's hint, so Definiciones and\n * Operaciones stay distinguishable in a flat list — the sidebar's top tier\n * (ADR 0033) surviving the flattening rather than being dropped by it.\n */\nexport function useNavSource(\n sections: readonly NavSection[],\n term: string,\n): CommandSource {\n const t = useT('shell');\n const translateKey = useTranslateKey();\n const withLocale = useLocaleHref();\n\n return useMemo(() => {\n const options: CommandOption[] = [];\n\n for (const section of sections) {\n // Only the screen type. Falling back to the section title here read as\n // \"Perfil · Cuenta · Cuenta\" on the untyped account section, because the\n // title is already the sublabel — one fact, printed twice.\n const hint =\n section.type === undefined\n ? undefined\n : translateKey(SCREEN_TYPE_LABEL_KEYS[section.type]);\n\n for (const item of section.items) {\n options.push({\n id: `nav:${item.href}`,\n label: item.label,\n href: withLocale(item.href),\n ...(hint === undefined ? {} : { hint }),\n ...(section.title === undefined ? {} : { sublabel: section.title }),\n });\n\n if (item.workspace !== undefined) {\n options.push({\n id: `nav:workspace:${item.workspace}`,\n label: item.label,\n href: withLocale(workspaceHref(item.workspace)),\n hint: t('commandPalette.openInWorkspaceHint'),\n ...(section.title === undefined ? {} : { sublabel: section.title }),\n });\n }\n }\n }\n\n return {\n key: 'navigation',\n groups: [\n {\n key: 'navigation',\n label: t('commandPalette.groups.navigation'),\n options: options.filter(option => matchesCommand(term, option)),\n isLoading: false,\n },\n ],\n } satisfies CommandSource;\n }, [sections, term, t, translateKey, withLocale]);\n}\n"],"names":["SCREEN_TYPE_LABEL_KEYS","matchesCommand","useT","useTranslateKey","useMemo","useLocaleHref","workspaceHref","workspace","encodeURIComponent","useNavSource","sections","term","t","translateKey","withLocale","options","section","hint","type","undefined","item","items","push","id","href","label","title","sublabel","key","groups","filter","option","isLoading"],"mappings":"AAAA;;AAEA,SAASA,sBAAsB,QAAQ,iBAAiB;AACxD,SAGEC,cAAc,QACT,gBAAgB;AACvB,SAASC,IAAI,EAAEC,eAAe,QAAQ,0BAA0B;AAChE,SAASC,OAAO,QAAQ,QAAQ;AAGhC,SAASC,aAAa,QAAQ,mBAAmB;AAEjD,0EAA0E,GAC1E,MAAMC,gBAAgB,CAACC,YACrB,CAAC,eAAe,EAAEC,mBAAmBD,YAAY;AAEnD;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASE,aACdC,QAA+B,EAC/BC,IAAY;IAEZ,MAAMC,IAAIV,KAAK;IACf,MAAMW,eAAeV;IACrB,MAAMW,aAAaT;IAEnB,OAAOD,QAAQ;QACb,MAAMW,UAA2B,EAAE;QAEnC,KAAK,MAAMC,WAAWN,SAAU;YAC9B,uEAAuE;YACvE,yEAAyE;YACzE,2DAA2D;YAC3D,MAAMO,OACJD,QAAQE,IAAI,KAAKC,YACbA,YACAN,aAAab,sBAAsB,CAACgB,QAAQE,IAAI,CAAC;YAEvD,KAAK,MAAME,QAAQJ,QAAQK,KAAK,CAAE;gBAChCN,QAAQO,IAAI,CAAC;oBACXC,IAAI,CAAC,IAAI,EAAEH,KAAKI,IAAI,EAAE;oBACtBC,OAAOL,KAAKK,KAAK;oBACjBD,MAAMV,WAAWM,KAAKI,IAAI;mBACtBP,SAASE,YAAY,CAAC,IAAI;oBAAEF;gBAAK,GACjCD,QAAQU,KAAK,KAAKP,YAAY,CAAC,IAAI;oBAAEQ,UAAUX,QAAQU,KAAK;gBAAC;gBAGnE,IAAIN,KAAKb,SAAS,KAAKY,WAAW;oBAChCJ,QAAQO,IAAI,CAAC;wBACXC,IAAI,CAAC,cAAc,EAAEH,KAAKb,SAAS,EAAE;wBACrCkB,OAAOL,KAAKK,KAAK;wBACjBD,MAAMV,WAAWR,cAAcc,KAAKb,SAAS;wBAC7CU,MAAML,EAAE;uBACJI,QAAQU,KAAK,KAAKP,YAAY,CAAC,IAAI;wBAAEQ,UAAUX,QAAQU,KAAK;oBAAC;gBAErE;YACF;QACF;QAEA,OAAO;YACLE,KAAK;YACLC,QAAQ;gBACN;oBACED,KAAK;oBACLH,OAAOb,EAAE;oBACTG,SAASA,QAAQe,MAAM,CAACC,CAAAA,SAAU9B,eAAeU,MAAMoB;oBACvDC,WAAW;gBACb;aACD;QACH;IACF,GAAG;QAACtB;QAAUC;QAAMC;QAAGC;QAAcC;KAAW;AAClD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CommandSource } from '@entifix/core';
|
|
2
|
+
/**
|
|
3
|
+
* Records matching the term, one group per source, in the order the host
|
|
4
|
+
* declared them.
|
|
5
|
+
*
|
|
6
|
+
* The whole of the *keystroke* policy lives here rather than in `searchRecords`,
|
|
7
|
+
* which is deliberately a bare typed `fetch`: the palette is the thing that
|
|
8
|
+
* knows a keystroke is not a query. So this owns the debounce, the abort and the
|
|
9
|
+
* two-character floor, while the route owns the fan-out, the per-source timeout
|
|
10
|
+
* and the authorization.
|
|
11
|
+
*
|
|
12
|
+
* **No index, ever.** Every option here came back from `/api/search` for this
|
|
13
|
+
* principal and this organization, on this keystroke. A prefetched client index
|
|
14
|
+
* is the shape that surfaces another organization's record the first time a
|
|
15
|
+
* session's scope moves under it, and ADR 0040 forecloses it permanently.
|
|
16
|
+
*
|
|
17
|
+
* Below the two-character floor the group is present and **says why** rather
|
|
18
|
+
* than being empty or absent: empty would claim nothing matched a search that
|
|
19
|
+
* never ran, and absent would leave someone wondering whether records are
|
|
20
|
+
* searchable here at all.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useRecordsSource(term: string, enabled: boolean): CommandSource;
|
|
23
|
+
//# sourceMappingURL=use-records-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-records-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-records-source.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EAEd,MAAM,eAAe,CAAC;AA8CvB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACf,aAAa,CAgJf"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
3
|
+
import { useT, useTranslateKey } from '@entifix/react-controls';
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useLocaleHref } from '../i18n/index.js';
|
|
6
|
+
import { searchRecords } from '../search/search-records.js';
|
|
7
|
+
import { RECORD_SEARCH_DEBOUNCE_MS, RECORD_SEARCH_MIN_TERM } from './palette-command.js';
|
|
8
|
+
/**
|
|
9
|
+
* Which reasons are the caller's ordinary state, and which mean something broke.
|
|
10
|
+
*
|
|
11
|
+
* The split is ADR 0040's, restated as severity because that is what the palette
|
|
12
|
+
* renders. An operator holds no membership, so every tenant-plane source answers
|
|
13
|
+
* `409` on every keystroke — painting that as an outage teaches them to ignore
|
|
14
|
+
* the row entirely, and then the `timeout` that does matter goes unread too.
|
|
15
|
+
*/ const SEVERITY = {
|
|
16
|
+
forbidden: 'scope',
|
|
17
|
+
noActiveOrganization: 'scope',
|
|
18
|
+
unauthenticated: 'scope',
|
|
19
|
+
timeout: 'reachability',
|
|
20
|
+
network: 'reachability',
|
|
21
|
+
invalidQuery: 'reachability',
|
|
22
|
+
notFound: 'reachability',
|
|
23
|
+
unexpected: 'reachability'
|
|
24
|
+
};
|
|
25
|
+
const NOTHING_YET = {
|
|
26
|
+
term: '',
|
|
27
|
+
failed: false
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Records matching the term, one group per source, in the order the host
|
|
31
|
+
* declared them.
|
|
32
|
+
*
|
|
33
|
+
* The whole of the *keystroke* policy lives here rather than in `searchRecords`,
|
|
34
|
+
* which is deliberately a bare typed `fetch`: the palette is the thing that
|
|
35
|
+
* knows a keystroke is not a query. So this owns the debounce, the abort and the
|
|
36
|
+
* two-character floor, while the route owns the fan-out, the per-source timeout
|
|
37
|
+
* and the authorization.
|
|
38
|
+
*
|
|
39
|
+
* **No index, ever.** Every option here came back from `/api/search` for this
|
|
40
|
+
* principal and this organization, on this keystroke. A prefetched client index
|
|
41
|
+
* is the shape that surfaces another organization's record the first time a
|
|
42
|
+
* session's scope moves under it, and ADR 0040 forecloses it permanently.
|
|
43
|
+
*
|
|
44
|
+
* Below the two-character floor the group is present and **says why** rather
|
|
45
|
+
* than being empty or absent: empty would claim nothing matched a search that
|
|
46
|
+
* never ran, and absent would leave someone wondering whether records are
|
|
47
|
+
* searchable here at all.
|
|
48
|
+
*/ export function useRecordsSource(term, enabled) {
|
|
49
|
+
const t = useT('shell');
|
|
50
|
+
const translateKey = useTranslateKey();
|
|
51
|
+
const withLocale = useLocaleHref();
|
|
52
|
+
const [state, setState] = useState(NOTHING_YET);
|
|
53
|
+
// The term the request in flight is for. A ref, not state: it changes on every
|
|
54
|
+
// keystroke and nothing renders from it.
|
|
55
|
+
const pending = useRef(null);
|
|
56
|
+
const active = enabled && term.length >= RECORD_SEARCH_MIN_TERM;
|
|
57
|
+
useEffect(()=>{
|
|
58
|
+
// Nothing is cleared here on purpose. A `setState` in an effect body is a
|
|
59
|
+
// cascading render, and there is nothing to clear: the memo below returns no
|
|
60
|
+
// groups while inactive, so a previous answer is unreachable rather than
|
|
61
|
+
// stale — and the host empties the term when it opens the palette.
|
|
62
|
+
if (!active) {
|
|
63
|
+
pending.current = null;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
pending.current = term;
|
|
68
|
+
const timer = setTimeout(()=>{
|
|
69
|
+
void searchRecords(term, {
|
|
70
|
+
signal: controller.signal
|
|
71
|
+
}).then((response)=>{
|
|
72
|
+
if (pending.current !== term) return;
|
|
73
|
+
setState({
|
|
74
|
+
term,
|
|
75
|
+
response,
|
|
76
|
+
failed: false
|
|
77
|
+
});
|
|
78
|
+
}).catch(()=>{
|
|
79
|
+
// An abort lands here too and must not be shown: the term moved on,
|
|
80
|
+
// so there is nothing to report about the request that did not finish.
|
|
81
|
+
if (controller.signal.aborted || pending.current !== term) return;
|
|
82
|
+
setState({
|
|
83
|
+
term,
|
|
84
|
+
failed: true
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}, RECORD_SEARCH_DEBOUNCE_MS);
|
|
88
|
+
return ()=>{
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
controller.abort();
|
|
91
|
+
};
|
|
92
|
+
}, [
|
|
93
|
+
active,
|
|
94
|
+
term
|
|
95
|
+
]);
|
|
96
|
+
return useMemo(()=>{
|
|
97
|
+
const heading = t('commandPalette.groups.records');
|
|
98
|
+
if (!active) {
|
|
99
|
+
// Someone who has typed one character is mid-word, not looking at an
|
|
100
|
+
// answer. Saying so is better than both alternatives: an empty group would
|
|
101
|
+
// claim nothing matched a search that never ran, and an absent one would
|
|
102
|
+
// leave them wondering whether records are searchable at all.
|
|
103
|
+
if (!enabled || term.length === 0) return {
|
|
104
|
+
key: 'records',
|
|
105
|
+
groups: []
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
key: 'records',
|
|
109
|
+
groups: [
|
|
110
|
+
{
|
|
111
|
+
key: 'records',
|
|
112
|
+
label: heading,
|
|
113
|
+
options: [],
|
|
114
|
+
isLoading: false,
|
|
115
|
+
unavailable: {
|
|
116
|
+
message: t('commandPalette.typeMore', {
|
|
117
|
+
count: RECORD_SEARCH_MIN_TERM
|
|
118
|
+
}),
|
|
119
|
+
severity: 'scope'
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// A newer term is in flight. The previous answer stays on screen with its
|
|
126
|
+
// groups marked loading rather than blanking: a list that empties between
|
|
127
|
+
// keystrokes flickers, and the rows it drops were correct a moment ago.
|
|
128
|
+
const isLoading = state.term !== term;
|
|
129
|
+
if (state.response === undefined) {
|
|
130
|
+
return {
|
|
131
|
+
key: 'records',
|
|
132
|
+
groups: [
|
|
133
|
+
_extends({
|
|
134
|
+
key: 'records',
|
|
135
|
+
label: heading,
|
|
136
|
+
options: [],
|
|
137
|
+
isLoading: !state.failed
|
|
138
|
+
}, state.failed ? {
|
|
139
|
+
unavailable: {
|
|
140
|
+
message: t('commandPalette.unavailableReach', {
|
|
141
|
+
reason: translateKey('errors:unexpected')
|
|
142
|
+
}),
|
|
143
|
+
severity: 'reachability'
|
|
144
|
+
}
|
|
145
|
+
} : {})
|
|
146
|
+
]
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const groups = state.response.groups.map((group)=>({
|
|
150
|
+
key: `record:${group.source}`,
|
|
151
|
+
// The entity's own `pluralKey`, so record search introduces no copy of
|
|
152
|
+
// its own and a group is titled exactly as its screen is.
|
|
153
|
+
label: translateKey(group.labelKey),
|
|
154
|
+
options: group.items.map((item)=>_extends({
|
|
155
|
+
id: `record:${group.source}:${item.id}`,
|
|
156
|
+
label: item.label
|
|
157
|
+
}, item.sublabel === undefined ? {} : {
|
|
158
|
+
sublabel: item.sublabel
|
|
159
|
+
}, {
|
|
160
|
+
href: withLocale(item.href)
|
|
161
|
+
})),
|
|
162
|
+
total: group.total,
|
|
163
|
+
isLoading
|
|
164
|
+
}));
|
|
165
|
+
for (const degraded of state.response.unavailable){
|
|
166
|
+
const severity = SEVERITY[degraded.reason];
|
|
167
|
+
groups.push({
|
|
168
|
+
key: `record:${degraded.source}`,
|
|
169
|
+
// Derived from the entity rather than from the source key, which is an
|
|
170
|
+
// identifier and would render as `product-specification`. Every
|
|
171
|
+
// searchable entity declares a `pluralKey` — `catalogSurface` throws
|
|
172
|
+
// without one — so this always resolves.
|
|
173
|
+
label: translateKey(`entity:${degraded.entity}.plural`),
|
|
174
|
+
options: [],
|
|
175
|
+
isLoading: false,
|
|
176
|
+
unavailable: {
|
|
177
|
+
message: t(severity === 'scope' ? 'commandPalette.unavailableScope' : 'commandPalette.unavailableReach', {
|
|
178
|
+
reason: translateKey(`errors:${degraded.reason}`)
|
|
179
|
+
}),
|
|
180
|
+
severity
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
key: 'records',
|
|
186
|
+
groups
|
|
187
|
+
};
|
|
188
|
+
}, [
|
|
189
|
+
active,
|
|
190
|
+
enabled,
|
|
191
|
+
state,
|
|
192
|
+
term,
|
|
193
|
+
t,
|
|
194
|
+
translateKey,
|
|
195
|
+
withLocale
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=use-records-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/command-palette/use-records-source.ts"],"sourcesContent":["'use client';\n\nimport type {\n CommandGroup,\n CommandSource,\n CommandUnavailableSeverity,\n} from '@entifix/core';\nimport { useT, useTranslateKey } from '@entifix/react-controls';\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useLocaleHref } from '../i18n/index.js';\nimport type {\n RecordSearchResponse,\n RecordSearchUnavailableReason,\n} from '../search/record-search.types.js';\nimport { searchRecords } from '../search/search-records.js';\nimport {\n RECORD_SEARCH_DEBOUNCE_MS,\n RECORD_SEARCH_MIN_TERM,\n} from './palette-command.js';\n\n/**\n * Which reasons are the caller's ordinary state, and which mean something broke.\n *\n * The split is ADR 0040's, restated as severity because that is what the palette\n * renders. An operator holds no membership, so every tenant-plane source answers\n * `409` on every keystroke — painting that as an outage teaches them to ignore\n * the row entirely, and then the `timeout` that does matter goes unread too.\n */\nconst SEVERITY: Readonly<\n Record<RecordSearchUnavailableReason, CommandUnavailableSeverity>\n> = {\n forbidden: 'scope',\n noActiveOrganization: 'scope',\n unauthenticated: 'scope',\n timeout: 'reachability',\n network: 'reachability',\n invalidQuery: 'reachability',\n notFound: 'reachability',\n unexpected: 'reachability',\n};\n\n/** What the browser is currently showing for records. */\ninterface RecordsState {\n /** The term this answer is for, so a stale response cannot overwrite a newer one. */\n readonly term: string;\n readonly response?: RecordSearchResponse;\n readonly failed: boolean;\n}\n\nconst NOTHING_YET: RecordsState = { term: '', failed: false };\n\n/**\n * Records matching the term, one group per source, in the order the host\n * declared them.\n *\n * The whole of the *keystroke* policy lives here rather than in `searchRecords`,\n * which is deliberately a bare typed `fetch`: the palette is the thing that\n * knows a keystroke is not a query. So this owns the debounce, the abort and the\n * two-character floor, while the route owns the fan-out, the per-source timeout\n * and the authorization.\n *\n * **No index, ever.** Every option here came back from `/api/search` for this\n * principal and this organization, on this keystroke. A prefetched client index\n * is the shape that surfaces another organization's record the first time a\n * session's scope moves under it, and ADR 0040 forecloses it permanently.\n *\n * Below the two-character floor the group is present and **says why** rather\n * than being empty or absent: empty would claim nothing matched a search that\n * never ran, and absent would leave someone wondering whether records are\n * searchable here at all.\n */\nexport function useRecordsSource(\n term: string,\n enabled: boolean,\n): CommandSource {\n const t = useT('shell');\n const translateKey = useTranslateKey();\n const withLocale = useLocaleHref();\n\n const [state, setState] = useState<RecordsState>(NOTHING_YET);\n // The term the request in flight is for. A ref, not state: it changes on every\n // keystroke and nothing renders from it.\n const pending = useRef<string | null>(null);\n\n const active = enabled && term.length >= RECORD_SEARCH_MIN_TERM;\n\n useEffect(() => {\n // Nothing is cleared here on purpose. A `setState` in an effect body is a\n // cascading render, and there is nothing to clear: the memo below returns no\n // groups while inactive, so a previous answer is unreachable rather than\n // stale — and the host empties the term when it opens the palette.\n if (!active) {\n pending.current = null;\n return;\n }\n\n const controller = new AbortController();\n pending.current = term;\n\n const timer = setTimeout(() => {\n void searchRecords(term, { signal: controller.signal })\n .then(response => {\n if (pending.current !== term) return;\n setState({ term, response, failed: false });\n })\n .catch(() => {\n // An abort lands here too and must not be shown: the term moved on,\n // so there is nothing to report about the request that did not finish.\n if (controller.signal.aborted || pending.current !== term) return;\n setState({ term, failed: true });\n });\n }, RECORD_SEARCH_DEBOUNCE_MS);\n\n return () => {\n clearTimeout(timer);\n controller.abort();\n };\n }, [active, term]);\n\n return useMemo(() => {\n const heading = t('commandPalette.groups.records');\n\n if (!active) {\n // Someone who has typed one character is mid-word, not looking at an\n // answer. Saying so is better than both alternatives: an empty group would\n // claim nothing matched a search that never ran, and an absent one would\n // leave them wondering whether records are searchable at all.\n if (!enabled || term.length === 0) return { key: 'records', groups: [] };\n return {\n key: 'records',\n groups: [\n {\n key: 'records',\n label: heading,\n options: [],\n isLoading: false,\n unavailable: {\n message: t('commandPalette.typeMore', {\n count: RECORD_SEARCH_MIN_TERM,\n }),\n severity: 'scope',\n },\n },\n ],\n };\n }\n // A newer term is in flight. The previous answer stays on screen with its\n // groups marked loading rather than blanking: a list that empties between\n // keystrokes flickers, and the rows it drops were correct a moment ago.\n const isLoading = state.term !== term;\n\n if (state.response === undefined) {\n return {\n key: 'records',\n groups: [\n {\n key: 'records',\n label: heading,\n options: [],\n isLoading: !state.failed,\n // The whole endpoint failed — a lapsed session or a `400`, both of\n // which arrive as a rejection rather than inside a `200`. It is\n // named for the same reason a single degraded source is.\n ...(state.failed\n ? {\n unavailable: {\n message: t('commandPalette.unavailableReach', {\n reason: translateKey('errors:unexpected'),\n }),\n severity: 'reachability' as const,\n },\n }\n : {}),\n },\n ],\n };\n }\n\n const groups: CommandGroup[] = state.response.groups.map(group => ({\n key: `record:${group.source}`,\n // The entity's own `pluralKey`, so record search introduces no copy of\n // its own and a group is titled exactly as its screen is.\n label: translateKey(group.labelKey),\n options: group.items.map(item => ({\n id: `record:${group.source}:${item.id}`,\n label: item.label,\n ...(item.sublabel === undefined ? {} : { sublabel: item.sublabel }),\n href: withLocale(item.href),\n })),\n total: group.total,\n isLoading,\n }));\n\n for (const degraded of state.response.unavailable) {\n const severity = SEVERITY[degraded.reason];\n groups.push({\n key: `record:${degraded.source}`,\n // Derived from the entity rather than from the source key, which is an\n // identifier and would render as `product-specification`. Every\n // searchable entity declares a `pluralKey` — `catalogSurface` throws\n // without one — so this always resolves.\n label: translateKey(`entity:${degraded.entity}.plural`),\n options: [],\n isLoading: false,\n unavailable: {\n message: t(\n severity === 'scope'\n ? 'commandPalette.unavailableScope'\n : 'commandPalette.unavailableReach',\n { reason: translateKey(`errors:${degraded.reason}`) },\n ),\n severity,\n },\n });\n }\n\n return { key: 'records', groups };\n }, [active, enabled, state, term, t, translateKey, withLocale]);\n}\n"],"names":["useT","useTranslateKey","useEffect","useMemo","useRef","useState","useLocaleHref","searchRecords","RECORD_SEARCH_DEBOUNCE_MS","RECORD_SEARCH_MIN_TERM","SEVERITY","forbidden","noActiveOrganization","unauthenticated","timeout","network","invalidQuery","notFound","unexpected","NOTHING_YET","term","failed","useRecordsSource","enabled","t","translateKey","withLocale","state","setState","pending","active","length","current","controller","AbortController","timer","setTimeout","signal","then","response","catch","aborted","clearTimeout","abort","heading","key","groups","label","options","isLoading","unavailable","message","count","severity","undefined","reason","map","group","source","labelKey","items","item","id","sublabel","href","total","degraded","push","entity"],"mappings":"AAAA;;AAOA,SAASA,IAAI,EAAEC,eAAe,QAAQ,0BAA0B;AAChE,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAE7D,SAASC,aAAa,QAAQ,mBAAmB;AAKjD,SAASC,aAAa,QAAQ,8BAA8B;AAC5D,SACEC,yBAAyB,EACzBC,sBAAsB,QACjB,uBAAuB;AAE9B;;;;;;;CAOC,GACD,MAAMC,WAEF;IACFC,WAAW;IACXC,sBAAsB;IACtBC,iBAAiB;IACjBC,SAAS;IACTC,SAAS;IACTC,cAAc;IACdC,UAAU;IACVC,YAAY;AACd;AAUA,MAAMC,cAA4B;IAAEC,MAAM;IAAIC,QAAQ;AAAM;AAE5D;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASC,iBACdF,IAAY,EACZG,OAAgB;IAEhB,MAAMC,IAAIxB,KAAK;IACf,MAAMyB,eAAexB;IACrB,MAAMyB,aAAapB;IAEnB,MAAM,CAACqB,OAAOC,SAAS,GAAGvB,SAAuBc;IACjD,+EAA+E;IAC/E,yCAAyC;IACzC,MAAMU,UAAUzB,OAAsB;IAEtC,MAAM0B,SAASP,WAAWH,KAAKW,MAAM,IAAItB;IAEzCP,UAAU;QACR,0EAA0E;QAC1E,6EAA6E;QAC7E,yEAAyE;QACzE,mEAAmE;QACnE,IAAI,CAAC4B,QAAQ;YACXD,QAAQG,OAAO,GAAG;YAClB;QACF;QAEA,MAAMC,aAAa,IAAIC;QACvBL,QAAQG,OAAO,GAAGZ;QAElB,MAAMe,QAAQC,WAAW;YACvB,KAAK7B,cAAca,MAAM;gBAAEiB,QAAQJ,WAAWI,MAAM;YAAC,GAClDC,IAAI,CAACC,CAAAA;gBACJ,IAAIV,QAAQG,OAAO,KAAKZ,MAAM;gBAC9BQ,SAAS;oBAAER;oBAAMmB;oBAAUlB,QAAQ;gBAAM;YAC3C,GACCmB,KAAK,CAAC;gBACL,oEAAoE;gBACpE,uEAAuE;gBACvE,IAAIP,WAAWI,MAAM,CAACI,OAAO,IAAIZ,QAAQG,OAAO,KAAKZ,MAAM;gBAC3DQ,SAAS;oBAAER;oBAAMC,QAAQ;gBAAK;YAChC;QACJ,GAAGb;QAEH,OAAO;YACLkC,aAAaP;YACbF,WAAWU,KAAK;QAClB;IACF,GAAG;QAACb;QAAQV;KAAK;IAEjB,OAAOjB,QAAQ;QACb,MAAMyC,UAAUpB,EAAE;QAElB,IAAI,CAACM,QAAQ;YACX,qEAAqE;YACrE,2EAA2E;YAC3E,yEAAyE;YACzE,8DAA8D;YAC9D,IAAI,CAACP,WAAWH,KAAKW,MAAM,KAAK,GAAG,OAAO;gBAAEc,KAAK;gBAAWC,QAAQ,EAAE;YAAC;YACvE,OAAO;gBACLD,KAAK;gBACLC,QAAQ;oBACN;wBACED,KAAK;wBACLE,OAAOH;wBACPI,SAAS,EAAE;wBACXC,WAAW;wBACXC,aAAa;4BACXC,SAAS3B,EAAE,2BAA2B;gCACpC4B,OAAO3C;4BACT;4BACA4C,UAAU;wBACZ;oBACF;iBACD;YACH;QACF;QACA,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,MAAMJ,YAAYtB,MAAMP,IAAI,KAAKA;QAEjC,IAAIO,MAAMY,QAAQ,KAAKe,WAAW;YAChC,OAAO;gBACLT,KAAK;gBACLC,QAAQ;oBACN;wBACED,KAAK;wBACLE,OAAOH;wBACPI,SAAS,EAAE;wBACXC,WAAW,CAACtB,MAAMN,MAAM;uBAIpBM,MAAMN,MAAM,GACZ;wBACE6B,aAAa;4BACXC,SAAS3B,EAAE,mCAAmC;gCAC5C+B,QAAQ9B,aAAa;4BACvB;4BACA4B,UAAU;wBACZ;oBACF,IACA,CAAC;iBAER;YACH;QACF;QAEA,MAAMP,SAAyBnB,MAAMY,QAAQ,CAACO,MAAM,CAACU,GAAG,CAACC,CAAAA,QAAU,CAAA;gBACjEZ,KAAK,CAAC,OAAO,EAAEY,MAAMC,MAAM,EAAE;gBAC7B,uEAAuE;gBACvE,0DAA0D;gBAC1DX,OAAOtB,aAAagC,MAAME,QAAQ;gBAClCX,SAASS,MAAMG,KAAK,CAACJ,GAAG,CAACK,CAAAA,OAAS;wBAChCC,IAAI,CAAC,OAAO,EAAEL,MAAMC,MAAM,CAAC,CAAC,EAAEG,KAAKC,EAAE,EAAE;wBACvCf,OAAOc,KAAKd,KAAK;uBACbc,KAAKE,QAAQ,KAAKT,YAAY,CAAC,IAAI;wBAAES,UAAUF,KAAKE,QAAQ;oBAAC;wBACjEC,MAAMtC,WAAWmC,KAAKG,IAAI;;gBAE5BC,OAAOR,MAAMQ,KAAK;gBAClBhB;YACF,CAAA;QAEA,KAAK,MAAMiB,YAAYvC,MAAMY,QAAQ,CAACW,WAAW,CAAE;YACjD,MAAMG,WAAW3C,QAAQ,CAACwD,SAASX,MAAM,CAAC;YAC1CT,OAAOqB,IAAI,CAAC;gBACVtB,KAAK,CAAC,OAAO,EAAEqB,SAASR,MAAM,EAAE;gBAChC,uEAAuE;gBACvE,gEAAgE;gBAChE,qEAAqE;gBACrE,yCAAyC;gBACzCX,OAAOtB,aAAa,CAAC,OAAO,EAAEyC,SAASE,MAAM,CAAC,OAAO,CAAC;gBACtDpB,SAAS,EAAE;gBACXC,WAAW;gBACXC,aAAa;oBACXC,SAAS3B,EACP6B,aAAa,UACT,oCACA,mCACJ;wBAAEE,QAAQ9B,aAAa,CAAC,OAAO,EAAEyC,SAASX,MAAM,EAAE;oBAAE;oBAEtDF;gBACF;YACF;QACF;QAEA,OAAO;YAAER,KAAK;YAAWC;QAAO;IAClC,GAAG;QAAChB;QAAQP;QAASI;QAAOP;QAAMI;QAAGC;QAAcC;KAAW;AAChE"}
|