@entifix/next-shell 0.5.0 → 0.7.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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/back-office/group-by-screen-type.d.ts +2 -2
- package/dist/lib/back-office/group-by-screen-type.js +2 -2
- package/dist/lib/back-office/group-by-screen-type.js.map +1 -1
- package/dist/lib/back-office/nav.d.ts +1 -2
- package/dist/lib/back-office/nav.d.ts.map +1 -1
- package/dist/lib/back-office/nav.js.map +1 -1
- package/dist/lib/back-office/sidebar-nav.d.ts +2 -2
- package/dist/lib/back-office/sidebar-nav.js +2 -2
- package/dist/lib/back-office/sidebar-nav.js.map +1 -1
- package/dist/lib/command-palette/command-palette-host.js +1 -1
- package/dist/lib/command-palette/command-palette-host.js.map +1 -1
- package/dist/lib/command-palette/use-command-recency.d.ts +1 -1
- package/dist/lib/command-palette/use-command-recency.js +1 -1
- package/dist/lib/command-palette/use-command-recency.js.map +1 -1
- package/dist/lib/command-palette/use-nav-source.d.ts +4 -3
- package/dist/lib/command-palette/use-nav-source.d.ts.map +1 -1
- package/dist/lib/command-palette/use-nav-source.js +4 -3
- package/dist/lib/command-palette/use-nav-source.js.map +1 -1
- package/dist/lib/command-palette/use-records-source.d.ts +1 -1
- package/dist/lib/command-palette/use-records-source.js +2 -2
- package/dist/lib/command-palette/use-records-source.js.map +1 -1
- package/dist/lib/command-palette/use-tabs-source.d.ts +1 -1
- package/dist/lib/command-palette/use-tabs-source.js +1 -1
- package/dist/lib/command-palette/use-tabs-source.js.map +1 -1
- package/dist/lib/command-palette/use-use-cases-source.js +2 -2
- package/dist/lib/command-palette/use-use-cases-source.js.map +1 -1
- package/dist/lib/config/service-proxy-route.d.ts +11 -2
- package/dist/lib/config/service-proxy-route.d.ts.map +1 -1
- package/dist/lib/config/service-proxy-route.js +27 -4
- package/dist/lib/config/service-proxy-route.js.map +1 -1
- package/dist/lib/crud/entity-crud-form.js +3 -3
- package/dist/lib/crud/entity-crud-form.js.map +1 -1
- package/dist/lib/crud/hand-off-write.d.ts +1 -1
- package/dist/lib/crud/hand-off-write.js +1 -1
- package/dist/lib/crud/hand-off-write.js.map +1 -1
- package/dist/lib/crud/make-entity-crud.d.ts.map +1 -1
- package/dist/lib/crud/make-entity-crud.js +5 -6
- package/dist/lib/crud/make-entity-crud.js.map +1 -1
- package/dist/lib/crud/make-entity-crud.types.d.ts +8 -10
- package/dist/lib/crud/make-entity-crud.types.d.ts.map +1 -1
- package/dist/lib/crud/make-entity-crud.types.js.map +1 -1
- package/dist/lib/crud/pending-notice.d.ts +2 -2
- package/dist/lib/crud/pending-notice.js +2 -2
- package/dist/lib/crud/pending-notice.js.map +1 -1
- package/dist/lib/crud/use-crud-render-link.d.ts.map +1 -1
- package/dist/lib/crud/use-crud-render-link.js +5 -1
- package/dist/lib/crud/use-crud-render-link.js.map +1 -1
- package/dist/lib/crud/use-entity-affordances.d.ts +1 -1
- package/dist/lib/crud/use-entity-affordances.js +1 -1
- package/dist/lib/crud/use-entity-affordances.js.map +1 -1
- package/dist/lib/crud/use-entity-bulk.d.ts +2 -2
- package/dist/lib/crud/use-entity-bulk.js +2 -2
- package/dist/lib/crud/use-entity-bulk.js.map +1 -1
- package/dist/lib/i18n/catalog/en.d.ts.map +1 -1
- package/dist/lib/i18n/catalog/en.js +1 -119
- package/dist/lib/i18n/catalog/en.js.map +1 -1
- package/dist/lib/i18n/catalog/es.d.ts +2 -131
- package/dist/lib/i18n/catalog/es.d.ts.map +1 -1
- package/dist/lib/i18n/catalog/es.js +2 -136
- package/dist/lib/i18n/catalog/es.js.map +1 -1
- package/dist/lib/i18n/catalog/index.d.ts +0 -226
- package/dist/lib/i18n/catalog/index.d.ts.map +1 -1
- package/dist/lib/i18n/spec-catalog.js +4 -4
- package/dist/lib/i18n/spec-catalog.js.map +1 -1
- package/dist/lib/search/record-search-route.d.ts +1 -1
- package/dist/lib/search/record-search-route.js +1 -1
- package/dist/lib/search/record-search-route.js.map +1 -1
- package/dist/lib/search/record-search-source.d.ts +1 -2
- package/dist/lib/search/record-search-source.d.ts.map +1 -1
- package/dist/lib/search/record-search-source.js.map +1 -1
- package/dist/lib/search/record-search.types.d.ts +1 -1
- package/dist/lib/search/record-search.types.js +1 -1
- package/dist/lib/search/record-search.types.js.map +1 -1
- package/dist/lib/session/account-links.js +1 -1
- package/dist/lib/session/account-links.js.map +1 -1
- package/dist/lib/session/session-keepalive.d.ts +2 -4
- package/dist/lib/session/session-keepalive.d.ts.map +1 -1
- package/dist/lib/session/session-keepalive.js +2 -4
- package/dist/lib/session/session-keepalive.js.map +1 -1
- package/dist/lib/session/use-session-refresh.js +1 -1
- package/dist/lib/session/use-session-refresh.js.map +1 -1
- package/dist/lib/wizard/wizard-tab-kind.d.ts +1 -2
- package/dist/lib/wizard/wizard-tab-kind.d.ts.map +1 -1
- package/dist/lib/wizard/wizard-tab-kind.js +1 -2
- package/dist/lib/wizard/wizard-tab-kind.js.map +1 -1
- package/dist/lib/workspace/drafts-state.d.ts.map +1 -1
- package/dist/lib/workspace/drafts-state.js +3 -2
- package/dist/lib/workspace/drafts-state.js.map +1 -1
- package/dist/lib/workspace/entity-tab-kind.d.ts +1 -2
- package/dist/lib/workspace/entity-tab-kind.d.ts.map +1 -1
- package/dist/lib/workspace/entity-tab-kind.js +1 -2
- package/dist/lib/workspace/entity-tab-kind.js.map +1 -1
- package/dist/lib/workspace/pending-state.d.ts +5 -4
- package/dist/lib/workspace/pending-state.d.ts.map +1 -1
- package/dist/lib/workspace/pending-state.js +1 -1
- package/dist/lib/workspace/pending-state.js.map +1 -1
- package/dist/lib/workspace/tabs-state.js +1 -1
- package/dist/lib/workspace/tabs-state.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/crud/make-entity-crud.types.ts"],"sourcesContent":["import type { EntityRepositoryTag } from '@entifix/business';\nimport type {\n BulkOutcome,\n Entity,\n EntityConstructor,\n EntityFieldDescriptor,\n EntityId,\n EntityMetadataSource,\n EntitySelection,\n} from '@entifix/core';\nimport type {\n EntityDraftStore,\n EntityLinkSourceConfig,\n} from '@entifix/react-integration';\nimport type { Context } from 'effect/Context';\nimport type { CustomTypeOptions } from 'i18next';\nimport type { ReactElement, ReactNode } from 'react';\n\n/**\n * The `entity` catalog keys that carry **both** form titles, a `label` and a\n * `plural`.\n *\n * Derived from the Spanish catalog rather than written out, so the union is the\n * catalog: an entity whose subtree has no `form.editTitle`/`form.newTitle`\n * cannot be handed to {@link makeEntityCrud} at all, and adding a reference\n * entity therefore starts by adding its copy to both locales. That is the whole\n * i18n gate for this factory — `@r10c/i18n-check` scans only\n * `packages/business/ts` for `@useCase()` decorators and never `.tsx`, so a\n * runtime key here would be checked by nothing.\n *\n * `label`/`plural` joined the condition when {@link EntityCrud} began carrying\n * them, so that a workspace tab and a nav entry derived from this descriptor\n * name the entity in the catalog's own words. They are part of the **type**\n * rather than a runtime assertion in the factory, because the catalog is\n * statically known here: a missing `plural` should fail at the call site that\n * generates the screens, not on the render that first tries to title a tab.\n *\n * Keeping the key typed is what lets the generated form call `useT('entity')`\n * with a template literal and still fail the build on a typo, instead of\n * reaching for the `useTranslateKey` escape hatch that authored copy must not\n * use. `AccountLabelKey` in `../session/account-links.ts` is the same trick\n * written out by hand; this one cannot drift because it is computed.\n */\nexport type EntityCatalogKey<\n TEntityCatalog = EntityCatalogOf<TypeOptionsResources>,\n> = [TEntityCatalog] extends [never]\n ? // No host augmentation in this compilation — this package's own specs, or\n // an adopter who has not declared one. The factory still works; the key is\n // simply unchecked, which is the same trade `useTranslateKey` makes.\n string\n : {\n [K in keyof TEntityCatalog]: TEntityCatalog[K] extends {\n form: { editTitle: string; newTitle: string };\n label: string;\n plural: string;\n }\n ? K\n : never;\n }[keyof TEntityCatalog] &\n string;\n\n/**\n * The host's `entity` namespace, read back out of its own `i18next`\n * augmentation.\n *\n * ⚠️ **The `entity` catalog is the host's, not entifix's.** It names *their*\n * entities — a parameter, an offering, a sales channel — so a framework that\n * shipped one would be shipping an application's product copy. Reading it back\n * through `CustomTypeOptions` keeps the key union computed rather than written\n * out, which is what stops it drifting, while leaving the catalog where it\n * belongs. A host that declares no augmentation gets `string`, and the factory\n * still works with the gate relaxed.\n */\ntype TypeOptionsResources = CustomTypeOptions extends { resources: infer R }\n ? R\n : never;\n\ntype EntityCatalogOf<TResources> = TResources extends { entity: infer E }\n ? E\n : never;\n\n/**\n * A relation the generated form edits with a picker.\n *\n * The target is named here rather than derived, because the common case is a\n * **plain foreign key**: when the target lives in another slice's store a typed\n * `EntityLink` is both an illegal import and a cross-store join, so the member\n * is a bare `string` (`ProductSpecification.brandId` into `catalog-reference`)\n * and its `@accessor()` has no vocabulary for \"the target's name\". Hence\n * `labelProperty`/`searchProperty` are stated at the call site instead of being\n * left to `describeEntityColumns`' `'name'` default, which happens to be right\n * today and states nothing.\n *\n * `searchProperty` must be `filterable` on the **target** or the service answers\n * `400`; `useEntityLinkSource` throws at render rather than letting that surface\n * as an empty suggestion list.\n */\nexport interface EntityCrudLink<TAdapters> {\n /** The owning entity's accessor name, e.g. `'brandId'`. */\n readonly field: string;\n readonly entityConstructor: EntityConstructor<Entity>;\n /**\n * Which adapter holds the **target's** repository. Often a different service\n * than the record's own: `catalog-reference` is another slice's store, so\n * resolving an id goes through that domain's own read path.\n */\n readonly repository: AdapterKey<TAdapters, Context<EntityRepositoryTag>>;\n /** Defaults to `'name'`. */\n readonly labelProperty?: string;\n /** Defaults to `'name'`. Must be `filterable` on the target. */\n readonly searchProperty?: string;\n}\n\n/** The keys of `TAdapters` whose value is assignable to `TValue`. */\nexport type AdapterKey<TAdapters, TValue> = {\n [K in keyof TAdapters]: TAdapters[K] extends TValue ? K : never;\n}[keyof TAdapters];\n\nexport interface EntityCrudOptions<TAdapters, TEntity extends Entity> {\n /** The domain shell's adapters hook, e.g. `useMarketplaceAdminAdapters`. */\n readonly useAdapters: () => TAdapters;\n /** Route prefix for both pages, e.g. `'/catalog/product-brand'`. */\n readonly basePath: string;\n /**\n * The entity's `entity` catalog key. Asserted against `@entity({ key })` at\n * factory time — the two are the same string by convention\n * (`entity:product-brand.label` mirrors `key: 'product-brand'`), and letting\n * them drift would title a form after a different entity.\n */\n readonly catalogKey: EntityCatalogKey;\n /** Which adapter holds this entity's own repository. */\n readonly repository: AdapterKey<TAdapters, Context<EntityRepositoryTag>>;\n /**\n * Members the form keeps but does not show — `id` always, plus anything a\n * transaction assigns. A hidden member still round-trips: it is dropped from\n * the rendered fields, not from the draft, so `reconstructEntity` carries it\n * back out of the seeded values.\n */\n readonly hiddenFields?: readonly string[];\n /** `<EntityColumn>` overrides for the list, for presentation metadata cannot express. */\n readonly columns?: ReactNode;\n /**\n * Extra controls beside the table's own — an `<EntityTableToolbar>`.\n *\n * Its own option rather than more `columns`, though both end up as\n * `EntityTable` children and the slots are sorted by component identity: a\n * toolbar filed under a columns prop is a name that lies, and the first thing\n * to go here is a link into a wizard, which is not a column by any reading\n * ([ADR 0045](../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).\n */\n readonly toolbar?: ReactNode;\n /** Frozen at factory time — see `use-entity-link-sources.ts`. */\n readonly links?: readonly EntityCrudLink<TAdapters>[];\n /**\n * Where the entity's served affordances come from — `GET /api/<entity>/$metadata`.\n *\n * Supplying it is what turns on everything ADR 0026 and ADR 0035 describe:\n * Save and Delete filtered against the caller's real grants, the declared\n * verbs on the form, the row overflow menu, and the bulk bar. Omitting it\n * keeps the pre-0026 behaviour exactly, which is what every generated catalog\n * rendered before this option existed.\n *\n * A `EntityMetadataSource` rather than a URL because the two hosts reach\n * their service differently — one through the config-driven REST adapters,\n * one through a shell's own same-origin routes.\n */\n readonly metadataSource?: EntityMetadataSource;\n /**\n * Which adapter holds this entity's {@link EntityMetadataSource} — the same\n * source as {@link metadataSource}, resolved from the host's composition\n * instead of built by the caller.\n *\n * Prefer it: a source is a transport, and naming it by key keeps the module\n * that generates the screens free of the adapter that fetches, exactly as\n * {@link repository} already does for records. Supplying both is a build\n * error — two answers to where the document comes from.\n */\n readonly metadata?: AdapterKey<TAdapters, EntityMetadataSource>;\n /**\n * Runs a `collection`-bound verb over a selection, and reports **per row**.\n *\n * Supplying it opts the list into the selection column and the bulk bar.\n * It is separate from `metadataSource` because the two are independent: an\n * entity can declare no collection verb and still want permission-filtered\n * Save, and the endpoint a verb posts to is the shell's knowledge, not\n * metadata's.\n */\n readonly runBulkUseCase?: (\n key: string,\n selection: EntitySelection<TEntity>,\n ) => Promise<readonly BulkOutcome[]>;\n /**\n * Runs an `entity`-bound verb on the record the single view is showing.\n *\n * The sibling of {@link runBulkUseCase}, and it had to exist for the same\n * reason: `EntityForm` has rendered these buttons since ADR 0035, and\n * `EntityCrudForm` has accepted an `onUseCase` for as long — but nothing ever\n * passed one, so a generated screen showed a declared, granted, served verb\n * that did nothing when clicked. Measured on `ProductOffering`'s `publish`,\n * which passed every `@r10c/slices` invariant and appeared nowhere.\n *\n * Resolving means the record changed, and the page reloads it: a verb that\n * moves a lifecycle leaves the form showing the old state otherwise, which\n * reads as \"the button did nothing\". Rejecting surfaces through the form's\n * own error slot, so a `409` from an illegal transition renders as the coded\n * message the catalog holds rather than a thrown promise nobody catches.\n */\n readonly runUseCase?: (key: string, id: EntityId) => Promise<void>;\n}\n\n/**\n * Props both hosts pass. A route host passes none — the slug comes from the URL\n * and a save returns to the list; the workspace tab host passes all of them.\n */\nexport interface EntityCrudSingleViewProps {\n /** Defaults to the `[slug]` route param. */\n readonly slug?: string;\n /** Defaults to navigating back to the list. */\n readonly onSaved?: () => void;\n readonly onDeleted?: () => void;\n /**\n * Where to autosave the edit. Supplying it is the whole opt-in: the form\n * seeds from the persisted draft, writes to it on every edit, and the page\n * clears it once a save or delete commits.\n *\n * A route host omits it and stays ephemeral, which is the rule stated as a\n * missing prop rather than as a flag — there is no value of \"absent\" that\n * accidentally starts persisting.\n */\n readonly draft?: EntityDraftStore;\n}\n\n/**\n * What the factory returns: a **named descriptor**, not four loose components.\n *\n * The identity fields are here so the workspace registry and the nav are derived\n * from a list of these rather than from the hand-written const maps they replace\n * (#141).\n */\nexport interface EntityCrud<TEntity extends Entity> {\n readonly entityConstructor: EntityConstructor<TEntity>;\n /** `@entity({ key })`, which is also `catalogKey` and the tab address payload. */\n readonly entityKey: EntityCatalogKey;\n /**\n * `@entity({ labelKey })` — how one record is named. A record tab's title.\n *\n * Read off the decorator rather than passed, so the tab strip, the nav and the\n * search results all name the entity from the one place it names itself.\n */\n readonly entityLabelKey: string;\n /** `@entity({ pluralKey })` — how the collection is named. A list tab's title. */\n readonly entityPluralKey: string;\n readonly basePath: string;\n readonly ListPage: () => ReactElement;\n readonly SingleViewPage: (props?: EntityCrudSingleViewProps) => ReactElement;\n}\n\n/**\n * A picker's transport with its context tags erased.\n *\n * The factory cannot preserve one type parameter per link — the pages that\n * build these know their adapters, the generated form only forwards them — and\n * `EntityForm.linkSources` is already `Record<string, EntityLinkSource<any>>`\n * for the same reason.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyEntityLinkSourceConfig = EntityLinkSourceConfig<Entity, any>;\n\n/**\n * One picker, resolved: the descriptor computed once at factory time, and the\n * use-cases plus adapter context the page builds per render.\n */\nexport interface EntityCrudLinkSource {\n readonly field: string;\n readonly descriptor: EntityFieldDescriptor;\n readonly config: AnyEntityLinkSourceConfig;\n}\n"],"names":[],"mappings":"AA4QA;;;CAGC,GACD,WAIC"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/crud/make-entity-crud.types.ts"],"sourcesContent":["import type { EntityRepositoryTag } from '@entifix/business';\nimport type {\n BulkOutcome,\n Entity,\n EntityConstructor,\n EntityFieldDescriptor,\n EntityId,\n EntityMetadataSource,\n EntitySelection,\n} from '@entifix/core';\nimport type {\n EntityDraftStore,\n EntityLinkSourceConfig,\n} from '@entifix/react-integration';\nimport type { Context } from 'effect/Context';\nimport type { CustomTypeOptions } from 'i18next';\nimport type { ReactElement, ReactNode } from 'react';\n\n/**\n * The `entity` catalog keys that carry **both** form titles, a `label` and a\n * `plural`.\n *\n * Derived from the Spanish catalog rather than written out, so the union is the\n * catalog: an entity whose subtree has no `form.editTitle`/`form.newTitle`\n * cannot be handed to {@link makeEntityCrud} at all, and adding a reference\n * entity therefore starts by adding its copy to both locales. That is the whole\n * i18n gate for this factory — a host's catalog check typically scans its\n * domain packages for `@useCase()` decorators and never `.tsx`, so a runtime\n * key here would be checked by nothing.\n *\n * `label`/`plural` joined the condition when {@link EntityCrud} began carrying\n * them, so that a workspace tab and a nav entry derived from this descriptor\n * name the entity in the catalog's own words. They are part of the **type**\n * rather than a runtime assertion in the factory, because the catalog is\n * statically known here: a missing `plural` should fail at the call site that\n * generates the screens, not on the render that first tries to title a tab.\n *\n * Keeping the key typed is what lets the generated form call `useT('entity')`\n * with a template literal and still fail the build on a typo, instead of\n * reaching for the `useTranslateKey` escape hatch that authored copy must not\n * use. `AccountLabelKey` in `../session/account-links.ts` is the same trick\n * written out by hand; this one cannot drift because it is computed.\n */\nexport type EntityCatalogKey<\n TEntityCatalog = EntityCatalogOf<TypeOptionsResources>,\n> = [TEntityCatalog] extends [never]\n ? // No host augmentation in this compilation — this package's own specs, or\n // an adopter who has not declared one. The factory still works; the key is\n // simply unchecked, which is the same trade `useTranslateKey` makes.\n string\n : {\n [K in keyof TEntityCatalog]: TEntityCatalog[K] extends {\n form: { editTitle: string; newTitle: string };\n label: string;\n plural: string;\n }\n ? K\n : never;\n }[keyof TEntityCatalog] &\n string;\n\n/**\n * The host's `entity` namespace, read back out of its own `i18next`\n * augmentation.\n *\n * ⚠️ **The `entity` catalog is the host's, not entifix's.** It names *their*\n * entities — a parameter, an offering, a sales channel — so a framework that\n * shipped one would be shipping an application's product copy. Reading it back\n * through `CustomTypeOptions` keeps the key union computed rather than written\n * out, which is what stops it drifting, while leaving the catalog where it\n * belongs. A host that declares no augmentation gets `string`, and the factory\n * still works with the gate relaxed.\n */\ntype TypeOptionsResources = CustomTypeOptions extends { resources: infer R }\n ? R\n : never;\n\ntype EntityCatalogOf<TResources> = TResources extends { entity: infer E }\n ? E\n : never;\n\n/**\n * A relation the generated form edits with a picker.\n *\n * The target is named here rather than derived, because the common case is a\n * **plain foreign key**: when the target lives in another slice's store a typed\n * `EntityLink` is both an illegal import and a cross-store join, so the member\n * is a bare `string` (`ProductSpecification.brandId` into `catalog-reference`)\n * and its `@accessor()` has no vocabulary for \"the target's name\". Hence\n * `labelProperty`/`searchProperty` are stated at the call site instead of being\n * left to `describeEntityColumns`' `'name'` default, which happens to be right\n * today and states nothing.\n *\n * `searchProperty` must be `filterable` on the **target** or the service answers\n * `400`; `useEntityLinkSource` throws at render rather than letting that surface\n * as an empty suggestion list.\n */\nexport interface EntityCrudLink<TAdapters> {\n /** The owning entity's accessor name, e.g. `'brandId'`. */\n readonly field: string;\n readonly entityConstructor: EntityConstructor<Entity>;\n /**\n * Which adapter holds the **target's** repository. Often a different service\n * than the record's own: `catalog-reference` is another slice's store, so\n * resolving an id goes through that domain's own read path.\n */\n readonly repository: AdapterKey<TAdapters, Context<EntityRepositoryTag>>;\n /** Defaults to `'name'`. */\n readonly labelProperty?: string;\n /** Defaults to `'name'`. Must be `filterable` on the target. */\n readonly searchProperty?: string;\n}\n\n/** The keys of `TAdapters` whose value is assignable to `TValue`. */\nexport type AdapterKey<TAdapters, TValue> = {\n [K in keyof TAdapters]: TAdapters[K] extends TValue ? K : never;\n}[keyof TAdapters];\n\nexport interface EntityCrudOptions<TAdapters, TEntity extends Entity> {\n /** The domain shell's adapters hook, e.g. `useMarketplaceAdminAdapters`. */\n readonly useAdapters: () => TAdapters;\n /** Route prefix for both pages, e.g. `'/catalog/product-brand'`. */\n readonly basePath: string;\n /**\n * The entity's `entity` catalog key. Asserted against `@entity({ key })` at\n * factory time — the two are the same string by convention\n * (`entity:product-brand.label` mirrors `key: 'product-brand'`), and letting\n * them drift would title a form after a different entity.\n */\n readonly catalogKey: EntityCatalogKey;\n /** Which adapter holds this entity's own repository. */\n readonly repository: AdapterKey<TAdapters, Context<EntityRepositoryTag>>;\n /**\n * Members the form keeps but does not show — `id` always, plus anything a\n * transaction assigns. A hidden member still round-trips: it is dropped from\n * the rendered fields, not from the draft, so `reconstructEntity` carries it\n * back out of the seeded values.\n */\n readonly hiddenFields?: readonly string[];\n /** `<EntityColumn>` overrides for the list, for presentation metadata cannot express. */\n readonly columns?: ReactNode;\n /**\n * Extra controls beside the table's own — an `<EntityTableToolbar>`.\n *\n * Its own option rather than more `columns`, though both end up as\n * `EntityTable` children and the slots are sorted by component identity: a\n * toolbar filed under a columns prop is a name that lies, and the first thing\n * to go here is a link into a wizard, which is not a column by any reading.\n */\n readonly toolbar?: ReactNode;\n /** Frozen at factory time — see `use-entity-link-sources.ts`. */\n readonly links?: readonly EntityCrudLink<TAdapters>[];\n /**\n * Where the entity's served affordances come from — `GET /api/<entity>/$metadata`.\n *\n * Supplying it is what turns on every served affordance:\n * Save and Delete filtered against the caller's real grants, the declared\n * verbs on the form, the row overflow menu, and the bulk bar. Omitting it\n * keeps the pre-0026 behaviour exactly, which is what every generated catalog\n * rendered before this option existed.\n *\n * A `EntityMetadataSource` rather than a URL because the two hosts reach\n * their service differently — one through the config-driven REST adapters,\n * one through a shell's own same-origin routes.\n */\n readonly metadataSource?: EntityMetadataSource;\n /**\n * Which adapter holds this entity's {@link EntityMetadataSource} — the same\n * source as {@link metadataSource}, resolved from the host's composition\n * instead of built by the caller.\n *\n * Prefer it: a source is a transport, and naming it by key keeps the module\n * that generates the screens free of the adapter that fetches, exactly as\n * {@link repository} already does for records. Supplying both is a build\n * error — two answers to where the document comes from.\n */\n readonly metadata?: AdapterKey<TAdapters, EntityMetadataSource>;\n /**\n * Runs a `collection`-bound verb over a selection, and reports **per row**.\n *\n * Supplying it opts the list into the selection column and the bulk bar.\n * It is separate from `metadataSource` because the two are independent: an\n * entity can declare no collection verb and still want permission-filtered\n * Save, and the endpoint a verb posts to is the shell's knowledge, not\n * metadata's.\n */\n readonly runBulkUseCase?: (\n key: string,\n selection: EntitySelection<TEntity>,\n ) => Promise<readonly BulkOutcome[]>;\n /**\n * Runs an `entity`-bound verb on the record the single view is showing.\n *\n * The sibling of {@link runBulkUseCase}, and it had to exist for the same\n * reason: `EntityForm` has rendered these buttons for a long time, and\n * `EntityCrudForm` has accepted an `onUseCase` for as long — but nothing ever\n * passed one, so a generated screen showed a declared, granted, served verb\n * that did nothing when clicked. Measured on `ProductOffering`'s `publish`,\n * which passed every check its host's register ran and appeared nowhere.\n *\n * Resolving means the record changed, and the page reloads it: a verb that\n * moves a lifecycle leaves the form showing the old state otherwise, which\n * reads as \"the button did nothing\". Rejecting surfaces through the form's\n * own error slot, so a `409` from an illegal transition renders as the coded\n * message the catalog holds rather than a thrown promise nobody catches.\n */\n readonly runUseCase?: (key: string, id: EntityId) => Promise<void>;\n}\n\n/**\n * Props both hosts pass. A route host passes none — the slug comes from the URL\n * and a save returns to the list; the workspace tab host passes all of them.\n */\nexport interface EntityCrudSingleViewProps {\n /** Defaults to the `[slug]` route param. */\n readonly slug?: string;\n /** Defaults to navigating back to the list. */\n readonly onSaved?: () => void;\n readonly onDeleted?: () => void;\n /**\n * Where to autosave the edit. Supplying it is the whole opt-in: the form\n * seeds from the persisted draft, writes to it on every edit, and the page\n * clears it once a save or delete commits.\n *\n * A route host omits it and stays ephemeral, which is the rule stated as a\n * missing prop rather than as a flag — there is no value of \"absent\" that\n * accidentally starts persisting.\n */\n readonly draft?: EntityDraftStore;\n}\n\n/**\n * What the factory returns: a **named descriptor**, not four loose components.\n *\n * The identity fields are here so the workspace registry and the nav are derived\n * from a list of these rather than from the hand-written const maps they replace.\n */\nexport interface EntityCrud<TEntity extends Entity> {\n readonly entityConstructor: EntityConstructor<TEntity>;\n /** `@entity({ key })`, which is also `catalogKey` and the tab address payload. */\n readonly entityKey: EntityCatalogKey;\n /**\n * `@entity({ labelKey })` — how one record is named. A record tab's title.\n *\n * Read off the decorator rather than passed, so the tab strip, the nav and the\n * search results all name the entity from the one place it names itself.\n */\n readonly entityLabelKey: string;\n /** `@entity({ pluralKey })` — how the collection is named. A list tab's title. */\n readonly entityPluralKey: string;\n readonly basePath: string;\n readonly ListPage: () => ReactElement;\n readonly SingleViewPage: (props?: EntityCrudSingleViewProps) => ReactElement;\n}\n\n/**\n * A picker's transport with its context tags erased.\n *\n * The factory cannot preserve one type parameter per link — the pages that\n * build these know their adapters, the generated form only forwards them — and\n * `EntityForm.linkSources` is already `Record<string, EntityLinkSource<any>>`\n * for the same reason.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyEntityLinkSourceConfig = EntityLinkSourceConfig<Entity, any>;\n\n/**\n * One picker, resolved: the descriptor computed once at factory time, and the\n * use-cases plus adapter context the page builds per render.\n */\nexport interface EntityCrudLinkSource {\n readonly field: string;\n readonly descriptor: EntityFieldDescriptor;\n readonly config: AnyEntityLinkSourceConfig;\n}\n"],"names":[],"mappings":"AA0QA;;;CAGC,GACD,WAIC"}
|
|
@@ -16,7 +16,7 @@ export interface PendingNoticeProps {
|
|
|
16
16
|
* `master:<key>` is the list and there is no address for a record that does not
|
|
17
17
|
* exist yet. A badge keyed on a pending record id would therefore be unreachable
|
|
18
18
|
* by construction, which is exactly the "declared, styled, never produced"
|
|
19
|
-
* defect
|
|
19
|
+
* defect the optimistic mutation contract exists to prevent. It renders here instead, where
|
|
20
20
|
* `afterSave()` actually puts the operator.
|
|
21
21
|
*
|
|
22
22
|
* `role="status"`, not `role="alert"` — the `BulkResult` reasoning: an outcome
|
|
@@ -25,7 +25,7 @@ export interface PendingNoticeProps {
|
|
|
25
25
|
* The failure headline is a catalog key and never `entry.reason`, which is free
|
|
26
26
|
* text from whatever threw, in whatever language that code was written in. The
|
|
27
27
|
* reason is kept as secondary, explicitly-unlocalized detail; a translatable
|
|
28
|
-
* failure needs an error *code* on the event, which
|
|
28
|
+
* failure needs an error *code* on the event, which the event does not carry yet.
|
|
29
29
|
*/
|
|
30
30
|
export declare function PendingNotice({ entries, onDismiss, className, }: PendingNoticeProps): import("react").JSX.Element | null;
|
|
31
31
|
//# sourceMappingURL=pending-notice.d.ts.map
|
|
@@ -11,7 +11,7 @@ import { Button, cn, useT } from '@entifix/react-controls/primitives';
|
|
|
11
11
|
* `master:<key>` is the list and there is no address for a record that does not
|
|
12
12
|
* exist yet. A badge keyed on a pending record id would therefore be unreachable
|
|
13
13
|
* by construction, which is exactly the "declared, styled, never produced"
|
|
14
|
-
* defect
|
|
14
|
+
* defect the optimistic mutation contract exists to prevent. It renders here instead, where
|
|
15
15
|
* `afterSave()` actually puts the operator.
|
|
16
16
|
*
|
|
17
17
|
* `role="status"`, not `role="alert"` — the `BulkResult` reasoning: an outcome
|
|
@@ -20,7 +20,7 @@ import { Button, cn, useT } from '@entifix/react-controls/primitives';
|
|
|
20
20
|
* The failure headline is a catalog key and never `entry.reason`, which is free
|
|
21
21
|
* text from whatever threw, in whatever language that code was written in. The
|
|
22
22
|
* reason is kept as secondary, explicitly-unlocalized detail; a translatable
|
|
23
|
-
* failure needs an error *code* on the event, which
|
|
23
|
+
* failure needs an error *code* on the event, which the event does not carry yet.
|
|
24
24
|
*/ export function PendingNotice({ entries, onDismiss, className }) {
|
|
25
25
|
// The `shell` namespace: this copy lives in `shell.ts`, and `useT()` with no
|
|
26
26
|
// argument reads `controls` — which resolves nothing and renders the key.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/crud/pending-notice.tsx"],"sourcesContent":["'use client';\n\nimport { Button, cn, useT } from '@entifix/react-controls/primitives';\nimport type { PendingEntry } from '@entifix/transactions';\n\nexport interface PendingNoticeProps {\n /** This entity's in-flight and failed writes, newest last. */\n entries: readonly PendingEntry[];\n /** Retires a failure the operator has read. */\n onDismiss: (transactionId: string) => void;\n className?: string;\n}\n\n/**\n * What became of the writes this browser started but the server has not\n * finished.\n *\n * ⚠️ **This is the list's surface because the tab strip cannot be.** Only\n * creates are transactional (`create: 'command'` fires on `isCreate` alone), and\n * a create has no workspace tab — `EntityEditorTab` takes a required id, so\n * `master:<key>` is the list and there is no address for a record that does not\n * exist yet. A badge keyed on a pending record id would therefore be unreachable\n * by construction, which is exactly the \"declared, styled, never produced\"\n * defect
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/crud/pending-notice.tsx"],"sourcesContent":["'use client';\n\nimport { Button, cn, useT } from '@entifix/react-controls/primitives';\nimport type { PendingEntry } from '@entifix/transactions';\n\nexport interface PendingNoticeProps {\n /** This entity's in-flight and failed writes, newest last. */\n entries: readonly PendingEntry[];\n /** Retires a failure the operator has read. */\n onDismiss: (transactionId: string) => void;\n className?: string;\n}\n\n/**\n * What became of the writes this browser started but the server has not\n * finished.\n *\n * ⚠️ **This is the list's surface because the tab strip cannot be.** Only\n * creates are transactional (`create: 'command'` fires on `isCreate` alone), and\n * a create has no workspace tab — `EntityEditorTab` takes a required id, so\n * `master:<key>` is the list and there is no address for a record that does not\n * exist yet. A badge keyed on a pending record id would therefore be unreachable\n * by construction, which is exactly the \"declared, styled, never produced\"\n * defect the optimistic mutation contract exists to prevent. It renders here instead, where\n * `afterSave()` actually puts the operator.\n *\n * `role=\"status\"`, not `role=\"alert\"` — the `BulkResult` reasoning: an outcome\n * to read, not an interruption.\n *\n * The failure headline is a catalog key and never `entry.reason`, which is free\n * text from whatever threw, in whatever language that code was written in. The\n * reason is kept as secondary, explicitly-unlocalized detail; a translatable\n * failure needs an error *code* on the event, which the event does not carry yet.\n */\nexport function PendingNotice({\n entries,\n onDismiss,\n className,\n}: PendingNoticeProps) {\n // The `shell` namespace: this copy lives in `shell.ts`, and `useT()` with no\n // argument reads `controls` — which resolves nothing and renders the key.\n const t = useT('shell');\n\n const saving = entries.filter(entry => entry.state === 'pending');\n const failed = entries.filter(entry => entry.state === 'failed');\n\n if (saving.length === 0 && failed.length === 0) return null;\n\n return (\n <div\n role=\"status\"\n aria-label={t('workspace.pendingLabel')}\n data-testid=\"pending-notice\"\n className={cn(\n 'flex flex-col gap-2xs rounded-lg border border-border bg-surface-elevated px-s py-2xs',\n className,\n )}\n >\n {saving.length > 0 && (\n <span className=\"text-step-sm text-content-muted\">\n {t('workspace.savePending', { count: saving.length })}\n </span>\n )}\n\n {failed.map(entry => (\n <div\n key={entry.transactionId}\n className=\"flex flex-wrap items-center gap-2xs\"\n >\n <span className=\"text-step-sm text-danger\">\n {t('workspace.saveFailed')}\n </span>\n {entry.reason !== undefined && (\n // Untranslated on purpose, and marked as such: it is a server\n // exception string, shown because a reason nobody can read is worse\n // than one in the wrong language.\n <span\n lang=\"und\"\n className=\"text-step-sm text-content-muted\"\n data-testid=\"pending-notice-reason\"\n >\n {entry.reason}\n </span>\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onDismiss(entry.transactionId)}\n >\n {t('workspace.saveFailedDismiss')}\n </Button>\n </div>\n ))}\n </div>\n );\n}\n"],"names":["Button","cn","useT","PendingNotice","entries","onDismiss","className","t","saving","filter","entry","state","failed","length","div","role","aria-label","data-testid","span","count","map","reason","undefined","lang","type","variant","size","onClick","transactionId"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,EAAE,EAAEC,IAAI,QAAQ,qCAAqC;AAWtE;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC,cAAc,EAC5BC,OAAO,EACPC,SAAS,EACTC,SAAS,EACU;IACnB,6EAA6E;IAC7E,0EAA0E;IAC1E,MAAMC,IAAIL,KAAK;IAEf,MAAMM,SAASJ,QAAQK,MAAM,CAACC,CAAAA,QAASA,MAAMC,KAAK,KAAK;IACvD,MAAMC,SAASR,QAAQK,MAAM,CAACC,CAAAA,QAASA,MAAMC,KAAK,KAAK;IAEvD,IAAIH,OAAOK,MAAM,KAAK,KAAKD,OAAOC,MAAM,KAAK,GAAG,OAAO;IAEvD,qBACE,MAACC;QACCC,MAAK;QACLC,cAAYT,EAAE;QACdU,eAAY;QACZX,WAAWL,GACT,yFACAK;;YAGDE,OAAOK,MAAM,GAAG,mBACf,KAACK;gBAAKZ,WAAU;0BACbC,EAAE,yBAAyB;oBAAEY,OAAOX,OAAOK,MAAM;gBAAC;;YAItDD,OAAOQ,GAAG,CAACV,CAAAA,sBACV,MAACI;oBAECR,WAAU;;sCAEV,KAACY;4BAAKZ,WAAU;sCACbC,EAAE;;wBAEJG,MAAMW,MAAM,KAAKC,aAChB,8DAA8D;wBAC9D,oEAAoE;wBACpE,kCAAkC;sCAClC,KAACJ;4BACCK,MAAK;4BACLjB,WAAU;4BACVW,eAAY;sCAEXP,MAAMW,MAAM;;sCAGjB,KAACrB;4BACCwB,MAAK;4BACLC,SAAQ;4BACRC,MAAK;4BACLC,SAAS,IAAMtB,UAAUK,MAAMkB,aAAa;sCAE3CrB,EAAE;;;mBAxBAG,MAAMkB,aAAa;;;AA8BlC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-crud-render-link.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/use-crud-render-link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAoB1D;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"use-crud-render-link.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/use-crud-render-link.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAoB1D;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAiC/D"}
|
|
@@ -24,7 +24,11 @@ import { CATALOG_NEW_SLUG } from './slug.js';
|
|
|
24
24
|
href: href,
|
|
25
25
|
className: className,
|
|
26
26
|
onClick: (event)=>{
|
|
27
|
-
|
|
27
|
+
// A page of the listing on screen is that listing's own address, so
|
|
28
|
+
// there is nothing for a tab host to open: the link is followed.
|
|
29
|
+
if (host === undefined || !isPlainClick(event) || intent.kind === 'page') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
28
32
|
event.preventDefault();
|
|
29
33
|
if (intent.kind === 'open') {
|
|
30
34
|
host.toEntity(entityKey, String(intent.id));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/crud/use-crud-render-link.tsx"],"sourcesContent":["'use client';\n\nimport type { RenderLink } from '@entifix/react-controls';\nimport Link from 'next/link';\nimport { type MouseEvent, useCallback } from 'react';\n\nimport { useEntityNavHost } from '../workspace/entity-nav.js';\nimport { CATALOG_NEW_SLUG } from './slug.js';\n\n/**\n * A click the page may take over: the primary button with no modifier. Anything\n * else — middle-click, ⌘/Ctrl-click, Shift-click — is the visitor asking for the\n * link itself, in a new tab or window, and must be left to the browser.\n */\nconst isPlainClick = (event: MouseEvent<HTMLAnchorElement>): boolean =>\n !event.defaultPrevented &&\n event.button === 0 &&\n !event.metaKey &&\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey;\n\n/**\n * How a generated page renders its Open, New and Back links (#20).\n *\n * A plain anchor was a full document load: the bundle rebuilt, every cache\n * emptied, and inside a workspace tab the click left the workspace for the route\n * page. So every link is `next/link` — a client-side navigation on a route host —\n * and, when a tab host is mounted, a plain click opens the matching tab instead:\n * the record, its create form (`<key>:new`), or the list. The `href` stays real\n * either way, so a modified click still opens the route in a new browser tab.\n */\nexport function useCrudRenderLink(entityKey: string): RenderLink {\n const host = useEntityNavHost();\n\n return useCallback<RenderLink>(\n ({ href, className, children, intent }) => (\n <Link\n href={href}\n className={className}\n onClick={event => {\n if (host === undefined
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/crud/use-crud-render-link.tsx"],"sourcesContent":["'use client';\n\nimport type { RenderLink } from '@entifix/react-controls';\nimport Link from 'next/link';\nimport { type MouseEvent, useCallback } from 'react';\n\nimport { useEntityNavHost } from '../workspace/entity-nav.js';\nimport { CATALOG_NEW_SLUG } from './slug.js';\n\n/**\n * A click the page may take over: the primary button with no modifier. Anything\n * else — middle-click, ⌘/Ctrl-click, Shift-click — is the visitor asking for the\n * link itself, in a new tab or window, and must be left to the browser.\n */\nconst isPlainClick = (event: MouseEvent<HTMLAnchorElement>): boolean =>\n !event.defaultPrevented &&\n event.button === 0 &&\n !event.metaKey &&\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey;\n\n/**\n * How a generated page renders its Open, New and Back links (#20).\n *\n * A plain anchor was a full document load: the bundle rebuilt, every cache\n * emptied, and inside a workspace tab the click left the workspace for the route\n * page. So every link is `next/link` — a client-side navigation on a route host —\n * and, when a tab host is mounted, a plain click opens the matching tab instead:\n * the record, its create form (`<key>:new`), or the list. The `href` stays real\n * either way, so a modified click still opens the route in a new browser tab.\n */\nexport function useCrudRenderLink(entityKey: string): RenderLink {\n const host = useEntityNavHost();\n\n return useCallback<RenderLink>(\n ({ href, className, children, intent }) => (\n <Link\n href={href}\n className={className}\n onClick={event => {\n // A page of the listing on screen is that listing's own address, so\n // there is nothing for a tab host to open: the link is followed.\n if (\n host === undefined ||\n !isPlainClick(event) ||\n intent.kind === 'page'\n ) {\n return;\n }\n event.preventDefault();\n if (intent.kind === 'open') {\n host.toEntity(entityKey, String(intent.id));\n } else if (intent.kind === 'new') {\n host.toEntity(entityKey, CATALOG_NEW_SLUG);\n } else {\n host.toList(entityKey);\n }\n }}\n >\n {children}\n </Link>\n ),\n [host, entityKey],\n );\n}\n"],"names":["Link","useCallback","useEntityNavHost","CATALOG_NEW_SLUG","isPlainClick","event","defaultPrevented","button","metaKey","ctrlKey","shiftKey","altKey","useCrudRenderLink","entityKey","host","href","className","children","intent","onClick","undefined","kind","preventDefault","toEntity","String","id","toList"],"mappings":"AAAA;;AAGA,OAAOA,UAAU,YAAY;AAC7B,SAA0BC,WAAW,QAAQ,QAAQ;AAErD,SAASC,gBAAgB,QAAQ,6BAA6B;AAC9D,SAASC,gBAAgB,QAAQ,YAAY;AAE7C;;;;CAIC,GACD,MAAMC,eAAe,CAACC,QACpB,CAACA,MAAMC,gBAAgB,IACvBD,MAAME,MAAM,KAAK,KACjB,CAACF,MAAMG,OAAO,IACd,CAACH,MAAMI,OAAO,IACd,CAACJ,MAAMK,QAAQ,IACf,CAACL,MAAMM,MAAM;AAEf;;;;;;;;;CASC,GACD,OAAO,SAASC,kBAAkBC,SAAiB;IACjD,MAAMC,OAAOZ;IAEb,OAAOD,YACL,CAAC,EAAEc,IAAI,EAAEC,SAAS,EAAEC,QAAQ,EAAEC,MAAM,EAAE,iBACpC,KAAClB;YACCe,MAAMA;YACNC,WAAWA;YACXG,SAASd,CAAAA;gBACP,oEAAoE;gBACpE,iEAAiE;gBACjE,IACES,SAASM,aACT,CAAChB,aAAaC,UACda,OAAOG,IAAI,KAAK,QAChB;oBACA;gBACF;gBACAhB,MAAMiB,cAAc;gBACpB,IAAIJ,OAAOG,IAAI,KAAK,QAAQ;oBAC1BP,KAAKS,QAAQ,CAACV,WAAWW,OAAON,OAAOO,EAAE;gBAC3C,OAAO,IAAIP,OAAOG,IAAI,KAAK,OAAO;oBAChCP,KAAKS,QAAQ,CAACV,WAAWV;gBAC3B,OAAO;oBACLW,KAAKY,MAAM,CAACb;gBACd;YACF;sBAECI;YAGL;QAACH;QAAMD;KAAU;AAErB"}
|
|
@@ -13,7 +13,7 @@ export interface EntityAffordances {
|
|
|
13
13
|
* two lines at every call site. An absent source is the hook's own business.
|
|
14
14
|
*
|
|
15
15
|
* Absent metadata is **not** a security posture: it is the shape both surfaces
|
|
16
|
-
* read as "behave exactly as before
|
|
16
|
+
* read as "behave exactly as before affordances were served". The route guard is the
|
|
17
17
|
* authorization boundary.
|
|
18
18
|
*/
|
|
19
19
|
export declare function useEntityAffordances<TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>, source: EntityMetadataSource | undefined): EntityAffordances;
|
|
@@ -9,7 +9,7 @@ import { useEntityUseCases } from '@entifix/react-integration';
|
|
|
9
9
|
* two lines at every call site. An absent source is the hook's own business.
|
|
10
10
|
*
|
|
11
11
|
* Absent metadata is **not** a security posture: it is the shape both surfaces
|
|
12
|
-
* read as "behave exactly as before
|
|
12
|
+
* read as "behave exactly as before affordances were served". The route guard is the
|
|
13
13
|
* authorization boundary.
|
|
14
14
|
*/ export function useEntityAffordances(entityConstructor, source) {
|
|
15
15
|
const { metadata, isLoading } = useEntityUseCases(entityConstructor, source);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/crud/use-entity-affordances.ts"],"sourcesContent":["'use client';\n\nimport type {\n Entity,\n EntityConstructor,\n EntityMetadataDocument,\n EntityMetadataSource,\n} from '@entifix/core';\nimport { useEntityUseCases } from '@entifix/react-integration';\n\n/** What a surface needs to render an entity's served affordances. */\nexport interface EntityAffordances {\n metadata?: EntityMetadataDocument;\n isMetadataLoading: boolean;\n}\n\n/**\n * Reads what this caller may do with an entity, in the shape a surface spreads.\n *\n * A thin rename over `useEntityUseCases` — `isLoading` becomes\n * `isMetadataLoading` — so a page can spread the result straight into\n * `EntityForm` or `EntityTable` beside its other props, rather than restating\n * two lines at every call site. An absent source is the hook's own business.\n *\n * Absent metadata is **not** a security posture: it is the shape both surfaces\n * read as \"behave exactly as before
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/crud/use-entity-affordances.ts"],"sourcesContent":["'use client';\n\nimport type {\n Entity,\n EntityConstructor,\n EntityMetadataDocument,\n EntityMetadataSource,\n} from '@entifix/core';\nimport { useEntityUseCases } from '@entifix/react-integration';\n\n/** What a surface needs to render an entity's served affordances. */\nexport interface EntityAffordances {\n metadata?: EntityMetadataDocument;\n isMetadataLoading: boolean;\n}\n\n/**\n * Reads what this caller may do with an entity, in the shape a surface spreads.\n *\n * A thin rename over `useEntityUseCases` — `isLoading` becomes\n * `isMetadataLoading` — so a page can spread the result straight into\n * `EntityForm` or `EntityTable` beside its other props, rather than restating\n * two lines at every call site. An absent source is the hook's own business.\n *\n * Absent metadata is **not** a security posture: it is the shape both surfaces\n * read as \"behave exactly as before affordances were served\". The route guard is the\n * authorization boundary.\n */\nexport function useEntityAffordances<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n source: EntityMetadataSource | undefined,\n): EntityAffordances {\n const { metadata, isLoading } = useEntityUseCases(entityConstructor, source);\n\n return { metadata, isMetadataLoading: isLoading };\n}\n"],"names":["useEntityUseCases","useEntityAffordances","entityConstructor","source","metadata","isLoading","isMetadataLoading"],"mappings":"AAAA;AAQA,SAASA,iBAAiB,QAAQ,6BAA6B;AAQ/D;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,qBACdC,iBAA6C,EAC7CC,MAAwC;IAExC,MAAM,EAAEC,QAAQ,EAAEC,SAAS,EAAE,GAAGL,kBAAkBE,mBAAmBC;IAErE,OAAO;QAAEC;QAAUE,mBAAmBD;IAAU;AAClD"}
|
|
@@ -28,8 +28,8 @@ export interface EntityBulkTableProps<TEntity extends Entity> {
|
|
|
28
28
|
* very navigation used to add to it would make "select rows across pages"
|
|
29
29
|
* impossible.
|
|
30
30
|
*
|
|
31
|
-
* Two behaviours worth not re-deriving. **The selection survives the action
|
|
32
|
-
*
|
|
31
|
+
* Two behaviours worth not re-deriving. **The selection survives the action**:
|
|
32
|
+
* after a run the rows stay ticked, because the operator's next act is
|
|
33
33
|
* usually to retry the failures or to run a second verb on the same rows, and
|
|
34
34
|
* re-selecting forty rows to do it is the whole cost the bulk bar exists to
|
|
35
35
|
* remove. And **a retry re-runs only the failures**, as an `ids` selection
|
|
@@ -15,8 +15,8 @@ import { useCallback, useState } from 'react';
|
|
|
15
15
|
* very navigation used to add to it would make "select rows across pages"
|
|
16
16
|
* impossible.
|
|
17
17
|
*
|
|
18
|
-
* Two behaviours worth not re-deriving. **The selection survives the action
|
|
19
|
-
*
|
|
18
|
+
* Two behaviours worth not re-deriving. **The selection survives the action**:
|
|
19
|
+
* after a run the rows stay ticked, because the operator's next act is
|
|
20
20
|
* usually to retry the failures or to run a second verb on the same rows, and
|
|
21
21
|
* re-selecting forty rows to do it is the whole cost the bulk bar exists to
|
|
22
22
|
* remove. And **a retry re-runs only the failures**, as an `ids` selection
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/crud/use-entity-bulk.ts"],"sourcesContent":["'use client';\n\nimport {\n type BulkOutcome,\n emptySelection,\n type Entity,\n type EntityId,\n type EntitySelection,\n} from '@entifix/core';\nimport { useCallback, useState } from 'react';\n\n/**\n * The code a row carries when the *request* failed rather than the row.\n *\n * `unexpected` is the shared fallback the `errors` catalog already ships, and\n * it is the honest label: what went wrong is not something this row did.\n */\nconst BULK_REQUEST_FAILED = 'unexpected';\n\nexport interface UseEntityBulkOptions<TEntity extends Entity> {\n /**\n * Runs one `collection`-bound verb. Absent means this listing has no bulk\n * actions, and no selection column is rendered at all.\n */\n readonly run?: (\n key: string,\n selection: EntitySelection<TEntity>,\n ) => Promise<readonly BulkOutcome[]>;\n /** Called after a run, so the listing can re-read the rows it just changed. */\n readonly onCompleted?: () => void;\n /** The whole request failed — a transport error, not a per-row outcome. */\n readonly onError?: (error: unknown) => void;\n}\n\n/** The props {@link useEntityBulk} contributes to `EntityTable`. */\nexport interface EntityBulkTableProps<TEntity extends Entity> {\n selection?: EntitySelection<TEntity>;\n onSelectionChange?: (selection: EntitySelection<TEntity>) => void;\n onBulkUseCase?: (key: string, selection: EntitySelection<TEntity>) => void;\n bulkOutcomes?: readonly BulkOutcome[];\n onBulkDismiss?: () => void;\n onBulkRetry?: (ids: EntityId[]) => void;\n isBulkRunning?: boolean;\n}\n\n/**\n * Owns a listing's selection and the result of the last bulk run.\n *\n * The selection lives here rather than inside `EntityTable` because it has to\n * survive pagination, and the page owns the pager — a selection reset by the\n * very navigation used to add to it would make \"select rows across pages\"\n * impossible.\n *\n * Two behaviours worth not re-deriving. **The selection survives the action
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/crud/use-entity-bulk.ts"],"sourcesContent":["'use client';\n\nimport {\n type BulkOutcome,\n emptySelection,\n type Entity,\n type EntityId,\n type EntitySelection,\n} from '@entifix/core';\nimport { useCallback, useState } from 'react';\n\n/**\n * The code a row carries when the *request* failed rather than the row.\n *\n * `unexpected` is the shared fallback the `errors` catalog already ships, and\n * it is the honest label: what went wrong is not something this row did.\n */\nconst BULK_REQUEST_FAILED = 'unexpected';\n\nexport interface UseEntityBulkOptions<TEntity extends Entity> {\n /**\n * Runs one `collection`-bound verb. Absent means this listing has no bulk\n * actions, and no selection column is rendered at all.\n */\n readonly run?: (\n key: string,\n selection: EntitySelection<TEntity>,\n ) => Promise<readonly BulkOutcome[]>;\n /** Called after a run, so the listing can re-read the rows it just changed. */\n readonly onCompleted?: () => void;\n /** The whole request failed — a transport error, not a per-row outcome. */\n readonly onError?: (error: unknown) => void;\n}\n\n/** The props {@link useEntityBulk} contributes to `EntityTable`. */\nexport interface EntityBulkTableProps<TEntity extends Entity> {\n selection?: EntitySelection<TEntity>;\n onSelectionChange?: (selection: EntitySelection<TEntity>) => void;\n onBulkUseCase?: (key: string, selection: EntitySelection<TEntity>) => void;\n bulkOutcomes?: readonly BulkOutcome[];\n onBulkDismiss?: () => void;\n onBulkRetry?: (ids: EntityId[]) => void;\n isBulkRunning?: boolean;\n}\n\n/**\n * Owns a listing's selection and the result of the last bulk run.\n *\n * The selection lives here rather than inside `EntityTable` because it has to\n * survive pagination, and the page owns the pager — a selection reset by the\n * very navigation used to add to it would make \"select rows across pages\"\n * impossible.\n *\n * Two behaviours worth not re-deriving. **The selection survives the action**:\n * after a run the rows stay ticked, because the operator's next act is\n * usually to retry the failures or to run a second verb on the same rows, and\n * re-selecting forty rows to do it is the whole cost the bulk bar exists to\n * remove. And **a retry re-runs only the failures**, as an `ids` selection\n * built from the outcomes — never the original selection, which would redo the\n * successful rows and, for a `matching` selection, re-resolve a filter whose\n * answer has just changed underneath it.\n */\nexport function useEntityBulk<TEntity extends Entity>({\n run,\n onCompleted,\n onError,\n}: UseEntityBulkOptions<TEntity>): {\n tableProps: EntityBulkTableProps<TEntity>;\n} {\n const [selection, setSelection] = useState<EntitySelection<TEntity>>(() =>\n emptySelection<TEntity>(),\n );\n const [outcomes, setOutcomes] = useState<readonly BulkOutcome[] | undefined>(\n undefined,\n );\n const [isRunning, setRunning] = useState(false);\n\n const execute = useCallback(\n async (key: string, target: EntitySelection<TEntity>) => {\n // Unreachable through the UI: the handlers that call this are only\n // exposed when `run` exists. It is here because a `useCallback` cannot\n // be declared conditionally and TypeScript still has to narrow.\n /* v8 ignore next */\n if (!run) return;\n setRunning(true);\n // The previous result is cleared as the new run starts: leaving it up\n // would show last run's failures beside this run's spinner, and they\n // are not the same rows.\n setOutcomes(undefined);\n try {\n setOutcomes(await run(key, target));\n } catch (error) {\n // The *request* failed — the network, a `403`, a `500` — which is not a\n // per-row outcome. Caught rather than left to reject: `void execute(…)`\n // at the call site would otherwise make every failed bulk run an\n // unhandled promise rejection, which is invisible in the UI and noisy\n // in the console.\n //\n // When the rows are known it is reported as every row failing, because\n // that is what happened, and it reuses the surface that already\n // attributes failures per row. A `matching` selection has no id list to\n // attribute to — that is the whole point of the mode — so it reports\n // nothing here and the error goes to the caller instead.\n if (target.mode === 'ids') {\n setOutcomes(\n // `Array.from`, never a spread — see `toWireSelection`: the loose\n // SWC helper wraps a `Set` instead of iterating it.\n Array.from(target.ids).map(id => ({\n id,\n ok: false,\n code: BULK_REQUEST_FAILED,\n })),\n );\n }\n onError?.(error);\n } finally {\n setRunning(false);\n onCompleted?.();\n }\n },\n [run, onCompleted, onError],\n );\n\n const [lastKey, setLastKey] = useState<string | undefined>(undefined);\n\n const onBulkUseCase = useCallback(\n (key: string, target: EntitySelection<TEntity>) => {\n setLastKey(key);\n void execute(key, target);\n },\n [execute],\n );\n\n const onBulkRetry = useCallback(\n (ids: EntityId[]) => {\n if (lastKey === undefined) return;\n void execute(lastKey, { mode: 'ids', ids: new Set(ids) });\n },\n [execute, lastKey],\n );\n\n if (!run) return { tableProps: {} };\n\n return {\n tableProps: {\n selection,\n onSelectionChange: setSelection,\n onBulkUseCase,\n bulkOutcomes: outcomes,\n onBulkDismiss: () => setOutcomes(undefined),\n onBulkRetry,\n isBulkRunning: isRunning,\n },\n };\n}\n"],"names":["emptySelection","useCallback","useState","BULK_REQUEST_FAILED","useEntityBulk","run","onCompleted","onError","selection","setSelection","outcomes","setOutcomes","undefined","isRunning","setRunning","execute","key","target","error","mode","Array","from","ids","map","id","ok","code","lastKey","setLastKey","onBulkUseCase","onBulkRetry","Set","tableProps","onSelectionChange","bulkOutcomes","onBulkDismiss","isBulkRunning"],"mappings":"AAAA;AAEA,SAEEA,cAAc,QAIT,gBAAgB;AACvB,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAQ;AAE9C;;;;;CAKC,GACD,MAAMC,sBAAsB;AA4B5B;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,cAAsC,EACpDC,GAAG,EACHC,WAAW,EACXC,OAAO,EACuB;IAG9B,MAAM,CAACC,WAAWC,aAAa,GAAGP,SAAmC,IACnEF;IAEF,MAAM,CAACU,UAAUC,YAAY,GAAGT,SAC9BU;IAEF,MAAM,CAACC,WAAWC,WAAW,GAAGZ,SAAS;IAEzC,MAAMa,UAAUd,YACd,OAAOe,KAAaC;QAClB,mEAAmE;QACnE,uEAAuE;QACvE,gEAAgE;QAChE,kBAAkB,GAClB,IAAI,CAACZ,KAAK;QACVS,WAAW;QACX,sEAAsE;QACtE,qEAAqE;QACrE,yBAAyB;QACzBH,YAAYC;QACZ,IAAI;YACFD,YAAY,MAAMN,IAAIW,KAAKC;QAC7B,EAAE,OAAOC,OAAO;YACd,wEAAwE;YACxE,wEAAwE;YACxE,iEAAiE;YACjE,sEAAsE;YACtE,kBAAkB;YAClB,EAAE;YACF,uEAAuE;YACvE,gEAAgE;YAChE,wEAAwE;YACxE,qEAAqE;YACrE,yDAAyD;YACzD,IAAID,OAAOE,IAAI,KAAK,OAAO;gBACzBR,YACE,kEAAkE;gBAClE,oDAAoD;gBACpDS,MAAMC,IAAI,CAACJ,OAAOK,GAAG,EAAEC,GAAG,CAACC,CAAAA,KAAO,CAAA;wBAChCA;wBACAC,IAAI;wBACJC,MAAMvB;oBACR,CAAA;YAEJ;YACAI,2BAAAA,QAAUW;QACZ,SAAU;YACRJ,WAAW;YACXR,+BAAAA;QACF;IACF,GACA;QAACD;QAAKC;QAAaC;KAAQ;IAG7B,MAAM,CAACoB,SAASC,WAAW,GAAG1B,SAA6BU;IAE3D,MAAMiB,gBAAgB5B,YACpB,CAACe,KAAaC;QACZW,WAAWZ;QACX,KAAKD,QAAQC,KAAKC;IACpB,GACA;QAACF;KAAQ;IAGX,MAAMe,cAAc7B,YAClB,CAACqB;QACC,IAAIK,YAAYf,WAAW;QAC3B,KAAKG,QAAQY,SAAS;YAAER,MAAM;YAAOG,KAAK,IAAIS,IAAIT;QAAK;IACzD,GACA;QAACP;QAASY;KAAQ;IAGpB,IAAI,CAACtB,KAAK,OAAO;QAAE2B,YAAY,CAAC;IAAE;IAElC,OAAO;QACLA,YAAY;YACVxB;YACAyB,mBAAmBxB;YACnBoB;YACAK,cAAcxB;YACdyB,eAAe,IAAMxB,YAAYC;YACjCkB;YACAM,eAAevB;QACjB;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../src/lib/i18n/catalog/en.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,cAAc,SAAS,EAAE,KAAK,CAAC;AAE9C,eAAO,MAAM,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../src/lib/i18n/catalog/en.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,cAAc,SAAS,EAAE,KAAK,CAAC;AAE9C,eAAO,MAAM,KAAK,EAAE,OAmFnB,CAAC"}
|
|
@@ -1,122 +1,4 @@
|
|
|
1
1
|
export const shell = {
|
|
2
|
-
/**
|
|
3
|
-
* The stock surface — the first Operaciones screens in the back office.
|
|
4
|
-
*
|
|
5
|
-
* The *tier* name ("Operaciones") comes from `nav.screenType.operation`; this
|
|
6
|
-
* names the group inside it.
|
|
7
|
-
*/ stock: {
|
|
8
|
-
nav: {
|
|
9
|
-
stock: 'Stock',
|
|
10
|
-
items: 'Stock on hand',
|
|
11
|
-
movements: 'Movement ledger',
|
|
12
|
-
reservations: 'Reservations'
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
order: {
|
|
16
|
-
nav: {
|
|
17
|
-
orders: 'Orders'
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
settlement: {
|
|
21
|
-
nav: {
|
|
22
|
-
settlement: 'Settlement',
|
|
23
|
-
agreements: 'Agreements',
|
|
24
|
-
entries: 'Commission entries',
|
|
25
|
-
runs: 'Settlement runs',
|
|
26
|
-
payouts: 'Vendor payouts'
|
|
27
|
-
},
|
|
28
|
-
agreement: {
|
|
29
|
-
channelRatesHint: 'Leave blank to charge the default commission on that channel. A 0 means nothing is charged.',
|
|
30
|
-
channelRateDefault: 'Default',
|
|
31
|
-
noChannelRates: 'No per-channel rates'
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
sales: {
|
|
35
|
-
nav: {
|
|
36
|
-
sales: 'Sales',
|
|
37
|
-
channels: 'Sales channels',
|
|
38
|
-
guided: 'Sales',
|
|
39
|
-
counterSale: 'Sell at the counter'
|
|
40
|
-
},
|
|
41
|
-
counterSale: {
|
|
42
|
-
title: 'Counter sale',
|
|
43
|
-
steps: {
|
|
44
|
-
channel: 'Channel',
|
|
45
|
-
lines: 'Products',
|
|
46
|
-
payment: 'Payment',
|
|
47
|
-
summary: 'Summary'
|
|
48
|
-
},
|
|
49
|
-
channelHint: 'Pick the channel this sale goes through.',
|
|
50
|
-
linesHint: 'Add the published products you are selling.',
|
|
51
|
-
addLine: 'Add product',
|
|
52
|
-
removeLine: 'Remove',
|
|
53
|
-
quantity: 'Quantity',
|
|
54
|
-
offering: 'Product',
|
|
55
|
-
total: 'Total',
|
|
56
|
-
paymentMethod: 'Payment method',
|
|
57
|
-
charge: 'Charge',
|
|
58
|
-
sold: 'Sale recorded',
|
|
59
|
-
soldDetail: 'The stock is already drawn down and the order is written.',
|
|
60
|
-
newSale: 'New sale',
|
|
61
|
-
emptyLines: 'Add at least one product to charge for.'
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
marketplaceAdmin: {
|
|
65
|
-
nav: {
|
|
66
|
-
catalog: 'Catalog',
|
|
67
|
-
products: 'Products',
|
|
68
|
-
brands: 'Brands',
|
|
69
|
-
categories: 'Categories',
|
|
70
|
-
offerings: 'Product offerings',
|
|
71
|
-
offeringPrices: 'Offering prices',
|
|
72
|
-
guided: 'Catalog'
|
|
73
|
-
},
|
|
74
|
-
wizard: {
|
|
75
|
-
productSetup: {
|
|
76
|
-
title: 'New product',
|
|
77
|
-
launch: 'Guided new product',
|
|
78
|
-
steps: {
|
|
79
|
-
start: 'Source',
|
|
80
|
-
source: 'Base product',
|
|
81
|
-
identity: 'Identification',
|
|
82
|
-
classification: 'Classification',
|
|
83
|
-
summary: 'Summary'
|
|
84
|
-
},
|
|
85
|
-
start: {
|
|
86
|
-
question: 'How do you want to start?',
|
|
87
|
-
blank: 'From scratch',
|
|
88
|
-
blankHint: 'Enter the details by hand.',
|
|
89
|
-
duplicate: 'Duplicate an existing one',
|
|
90
|
-
duplicateHint: 'Start from a product already on file. The code is not copied: it identifies the original.'
|
|
91
|
-
},
|
|
92
|
-
source: {
|
|
93
|
-
prompt: 'Pick the product to start from.',
|
|
94
|
-
chosen: 'Base product: {{name}}',
|
|
95
|
-
required: 'Pick a product to continue.'
|
|
96
|
-
},
|
|
97
|
-
summary: {
|
|
98
|
-
prompt: 'Check what you entered before creating it.',
|
|
99
|
-
origin: 'Source',
|
|
100
|
-
originBlank: 'From scratch',
|
|
101
|
-
originDuplicate: 'Duplicated from {{name}}',
|
|
102
|
-
empty: 'Not set'
|
|
103
|
-
},
|
|
104
|
-
recap: 'You had already answered:'
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
systemManagement: {
|
|
109
|
-
nav: {
|
|
110
|
-
section: 'System',
|
|
111
|
-
configuration: 'Configuration'
|
|
112
|
-
},
|
|
113
|
-
configuration: {
|
|
114
|
-
title: 'Platform configuration',
|
|
115
|
-
description: 'Parameters every service resolves at boot. Editing a value affects that service on its next start.',
|
|
116
|
-
secretPlaceholder: '•••••• (unchanged)',
|
|
117
|
-
secretHint: 'Leaving this blank keeps the stored value. Typing a new one replaces it.'
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
2
|
nav: {
|
|
121
3
|
primary: 'Primary',
|
|
122
4
|
openInWorkspace: 'Open {{label}} in workspace',
|
|
@@ -124,7 +6,7 @@ export const shell = {
|
|
|
124
6
|
openInNewTab: 'Open {{label}} in a new tab',
|
|
125
7
|
openInNewTabShort: 'Open in a new tab',
|
|
126
8
|
/**
|
|
127
|
-
* The screen taxonomy
|
|
9
|
+
* The screen taxonomy. Translated like every other string rather
|
|
128
10
|
* than kept in Spanish: what that decision fixed was which *Spanish* word
|
|
129
11
|
* ships to the default locale, not that the term is untranslatable.
|
|
130
12
|
*/ screenType: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/i18n/catalog/en.ts"],"sourcesContent":["type EsShell = typeof import('./es.js').shell;\n\nexport const shell: EsShell = {\n /**\n * The stock surface — the first Operaciones screens in the back office.\n *\n * The *tier* name (\"Operaciones\") comes from `nav.screenType.operation`; this\n * names the group inside it.\n */\n stock: {\n nav: {\n stock: 'Stock',\n items: 'Stock on hand',\n movements: 'Movement ledger',\n reservations: 'Reservations',\n },\n },\n order: {\n nav: {\n orders: 'Orders',\n },\n },\n settlement: {\n nav: {\n settlement: 'Settlement',\n agreements: 'Agreements',\n entries: 'Commission entries',\n runs: 'Settlement runs',\n payouts: 'Vendor payouts',\n },\n agreement: {\n channelRatesHint:\n 'Leave blank to charge the default commission on that channel. A 0 means nothing is charged.',\n channelRateDefault: 'Default',\n noChannelRates: 'No per-channel rates',\n },\n },\n sales: {\n nav: {\n sales: 'Sales',\n channels: 'Sales channels',\n guided: 'Sales',\n counterSale: 'Sell at the counter',\n },\n counterSale: {\n title: 'Counter sale',\n steps: {\n channel: 'Channel',\n lines: 'Products',\n payment: 'Payment',\n summary: 'Summary',\n },\n channelHint: 'Pick the channel this sale goes through.',\n linesHint: 'Add the published products you are selling.',\n addLine: 'Add product',\n removeLine: 'Remove',\n quantity: 'Quantity',\n offering: 'Product',\n total: 'Total',\n paymentMethod: 'Payment method',\n charge: 'Charge',\n sold: 'Sale recorded',\n soldDetail: 'The stock is already drawn down and the order is written.',\n newSale: 'New sale',\n emptyLines: 'Add at least one product to charge for.',\n },\n },\n marketplaceAdmin: {\n nav: {\n catalog: 'Catalog',\n products: 'Products',\n brands: 'Brands',\n categories: 'Categories',\n offerings: 'Product offerings',\n offeringPrices: 'Offering prices',\n guided: 'Catalog',\n },\n wizard: {\n productSetup: {\n title: 'New product',\n launch: 'Guided new product',\n steps: {\n start: 'Source',\n source: 'Base product',\n identity: 'Identification',\n classification: 'Classification',\n summary: 'Summary',\n },\n start: {\n question: 'How do you want to start?',\n blank: 'From scratch',\n blankHint: 'Enter the details by hand.',\n duplicate: 'Duplicate an existing one',\n duplicateHint:\n 'Start from a product already on file. The code is not copied: it identifies the original.',\n },\n source: {\n prompt: 'Pick the product to start from.',\n chosen: 'Base product: {{name}}',\n required: 'Pick a product to continue.',\n },\n summary: {\n prompt: 'Check what you entered before creating it.',\n origin: 'Source',\n originBlank: 'From scratch',\n originDuplicate: 'Duplicated from {{name}}',\n empty: 'Not set',\n },\n recap: 'You had already answered:',\n },\n },\n },\n systemManagement: {\n nav: {\n section: 'System',\n configuration: 'Configuration',\n },\n configuration: {\n title: 'Platform configuration',\n description:\n 'Parameters every service resolves at boot. Editing a value affects that service on its next start.',\n secretPlaceholder: '•••••• (unchanged)',\n secretHint:\n 'Leaving this blank keeps the stored value. Typing a new one replaces it.',\n },\n },\n nav: {\n primary: 'Primary',\n openInWorkspace: 'Open {{label}} in workspace',\n openInWorkspaceShort: 'Open in workspace',\n openInNewTab: 'Open {{label}} in a new tab',\n openInNewTabShort: 'Open in a new tab',\n /**\n * The screen taxonomy (ADR 0033). Translated like every other string rather\n * than kept in Spanish: what that decision fixed was which *Spanish* word\n * ships to the default locale, not that the term is untranslatable.\n */\n screenType: {\n master: 'Definitions',\n operation: 'Operations',\n wizard: 'Wizards',\n report: 'Reports',\n },\n },\n commandPalette: {\n open: 'Search or run',\n title: 'Search or run',\n placeholder: 'Search a record, a screen or a command…',\n newPlaceholder: 'What would you like to create?',\n newLabel: 'New…',\n newTitle: 'New',\n back: 'Back',\n empty: 'No results',\n loading: 'Searching…',\n more_one: '{{count}} more',\n more_other: '{{count}} more',\n typeMore: 'Type at least {{count}} characters to search records.',\n groups: {\n actions: 'Actions',\n commands: 'Commands',\n navigation: 'Navigation',\n tabs: 'Open tabs',\n records: 'Records',\n },\n unavailableScope: 'Not available here: {{reason}}',\n unavailableReach: 'We could not reach this source: {{reason}}',\n openInWorkspaceHint: 'Tab',\n },\n sidebar: {\n expand: 'Expand sidebar',\n collapse: 'Collapse sidebar',\n openMenu: 'Open the menu',\n closeMenu: 'Close the menu',\n menu: 'Navigation menu',\n expandGroup: 'Expand {{group}}',\n collapseGroup: 'Collapse {{group}}',\n },\n session: {\n expiringSoon:\n 'Your session is about to expire. Save your work and sign in again to continue.',\n },\n breadcrumbs: {\n home: 'Home',\n // The `/new` segment, which every domain's create route shares. It was\n // an auth-specific string until one host started serving two domains,\n // at which point a catalog page's breadcrumb read \"New user\".\n new: 'New',\n },\n account: {\n profile: 'Profile',\n security: 'Security',\n sessions: 'Sessions',\n signOut: 'Sign out',\n },\n workspace: {\n copyLink: 'Copy link',\n empty: 'No open tabs. Pick a catalog from the sidebar to start.',\n unsupported: \"This tab can't be opened here.\",\n discardTitle: 'Discard changes',\n discard: 'This tab has unsaved changes. Closing it will lose them.',\n discardConfirm: 'Discard',\n // See the Spanish catalog for why a failure's reason is not a key here.\n pendingLabel: 'Save status',\n savePending_one: 'Saving 1 record…',\n savePending_other: 'Saving {{count}} records…',\n saveFailed: 'The record could not be saved.',\n saveFailedDismiss: 'Dismiss',\n },\n};\n"],"names":["shell","stock","nav","items","movements","reservations","order","orders","settlement","agreements","entries","runs","payouts","agreement","channelRatesHint","channelRateDefault","noChannelRates","sales","channels","guided","counterSale","title","steps","channel","lines","payment","summary","channelHint","linesHint","addLine","removeLine","quantity","offering","total","paymentMethod","charge","sold","soldDetail","newSale","emptyLines","marketplaceAdmin","catalog","products","brands","categories","offerings","offeringPrices","wizard","productSetup","launch","start","source","identity","classification","question","blank","blankHint","duplicate","duplicateHint","prompt","chosen","required","origin","originBlank","originDuplicate","empty","recap","systemManagement","section","configuration","description","secretPlaceholder","secretHint","primary","openInWorkspace","openInWorkspaceShort","openInNewTab","openInNewTabShort","screenType","master","operation","report","commandPalette","open","placeholder","newPlaceholder","newLabel","newTitle","back","loading","more_one","more_other","typeMore","groups","actions","commands","navigation","tabs","records","unavailableScope","unavailableReach","openInWorkspaceHint","sidebar","expand","collapse","openMenu","closeMenu","menu","expandGroup","collapseGroup","session","expiringSoon","breadcrumbs","home","new","account","profile","security","sessions","signOut","workspace","copyLink","unsupported","discardTitle","discard","discardConfirm","pendingLabel","savePending_one","savePending_other","saveFailed","saveFailedDismiss"],"mappings":"AAEA,OAAO,MAAMA,QAAiB;IAC5B;;;;;GAKC,GACDC,OAAO;QACLC,KAAK;YACHD,OAAO;YACPE,OAAO;YACPC,WAAW;YACXC,cAAc;QAChB;IACF;IACAC,OAAO;QACLJ,KAAK;YACHK,QAAQ;QACV;IACF;IACAC,YAAY;QACVN,KAAK;YACHM,YAAY;YACZC,YAAY;YACZC,SAAS;YACTC,MAAM;YACNC,SAAS;QACX;QACAC,WAAW;YACTC,kBACE;YACFC,oBAAoB;YACpBC,gBAAgB;QAClB;IACF;IACAC,OAAO;QACLf,KAAK;YACHe,OAAO;YACPC,UAAU;YACVC,QAAQ;YACRC,aAAa;QACf;QACAA,aAAa;YACXC,OAAO;YACPC,OAAO;gBACLC,SAAS;gBACTC,OAAO;gBACPC,SAAS;gBACTC,SAAS;YACX;YACAC,aAAa;YACbC,WAAW;YACXC,SAAS;YACTC,YAAY;YACZC,UAAU;YACVC,UAAU;YACVC,OAAO;YACPC,eAAe;YACfC,QAAQ;YACRC,MAAM;YACNC,YAAY;YACZC,SAAS;YACTC,YAAY;QACd;IACF;IACAC,kBAAkB;QAChBtC,KAAK;YACHuC,SAAS;YACTC,UAAU;YACVC,QAAQ;YACRC,YAAY;YACZC,WAAW;YACXC,gBAAgB;YAChB3B,QAAQ;QACV;QACA4B,QAAQ;YACNC,cAAc;gBACZ3B,OAAO;gBACP4B,QAAQ;gBACR3B,OAAO;oBACL4B,OAAO;oBACPC,QAAQ;oBACRC,UAAU;oBACVC,gBAAgB;oBAChB3B,SAAS;gBACX;gBACAwB,OAAO;oBACLI,UAAU;oBACVC,OAAO;oBACPC,WAAW;oBACXC,WAAW;oBACXC,eACE;gBACJ;gBACAP,QAAQ;oBACNQ,QAAQ;oBACRC,QAAQ;oBACRC,UAAU;gBACZ;gBACAnC,SAAS;oBACPiC,QAAQ;oBACRG,QAAQ;oBACRC,aAAa;oBACbC,iBAAiB;oBACjBC,OAAO;gBACT;gBACAC,OAAO;YACT;QACF;IACF;IACAC,kBAAkB;QAChBjE,KAAK;YACHkE,SAAS;YACTC,eAAe;QACjB;QACAA,eAAe;YACbhD,OAAO;YACPiD,aACE;YACFC,mBAAmB;YACnBC,YACE;QACJ;IACF;IACAtE,KAAK;QACHuE,SAAS;QACTC,iBAAiB;QACjBC,sBAAsB;QACtBC,cAAc;QACdC,mBAAmB;QACnB;;;;KAIC,GACDC,YAAY;YACVC,QAAQ;YACRC,WAAW;YACXjC,QAAQ;YACRkC,QAAQ;QACV;IACF;IACAC,gBAAgB;QACdC,MAAM;QACN9D,OAAO;QACP+D,aAAa;QACbC,gBAAgB;QAChBC,UAAU;QACVC,UAAU;QACVC,MAAM;QACNvB,OAAO;QACPwB,SAAS;QACTC,UAAU;QACVC,YAAY;QACZC,UAAU;QACVC,QAAQ;YACNC,SAAS;YACTC,UAAU;YACVC,YAAY;YACZC,MAAM;YACNC,SAAS;QACX;QACAC,kBAAkB;QAClBC,kBAAkB;QAClBC,qBAAqB;IACvB;IACAC,SAAS;QACPC,QAAQ;QACRC,UAAU;QACVC,UAAU;QACVC,WAAW;QACXC,MAAM;QACNC,aAAa;QACbC,eAAe;IACjB;IACAC,SAAS;QACPC,cACE;IACJ;IACAC,aAAa;QACXC,MAAM;QACN,uEAAuE;QACvE,sEAAsE;QACtE,8DAA8D;QAC9DC,KAAK;IACP;IACAC,SAAS;QACPC,SAAS;QACTC,UAAU;QACVC,UAAU;QACVC,SAAS;IACX;IACAC,WAAW;QACTC,UAAU;QACVxD,OAAO;QACPyD,aAAa;QACbC,cAAc;QACdC,SAAS;QACTC,gBAAgB;QAChB,wEAAwE;QACxEC,cAAc;QACdC,iBAAiB;QACjBC,mBAAmB;QACnBC,YAAY;QACZC,mBAAmB;IACrB;AACF,EAAE"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/i18n/catalog/en.ts"],"sourcesContent":["type EsShell = typeof import('./es.js').shell;\n\nexport const shell: EsShell = {\n nav: {\n primary: 'Primary',\n openInWorkspace: 'Open {{label}} in workspace',\n openInWorkspaceShort: 'Open in workspace',\n openInNewTab: 'Open {{label}} in a new tab',\n openInNewTabShort: 'Open in a new tab',\n /**\n * The screen taxonomy. Translated like every other string rather\n * than kept in Spanish: what that decision fixed was which *Spanish* word\n * ships to the default locale, not that the term is untranslatable.\n */\n screenType: {\n master: 'Definitions',\n operation: 'Operations',\n wizard: 'Wizards',\n report: 'Reports',\n },\n },\n commandPalette: {\n open: 'Search or run',\n title: 'Search or run',\n placeholder: 'Search a record, a screen or a command…',\n newPlaceholder: 'What would you like to create?',\n newLabel: 'New…',\n newTitle: 'New',\n back: 'Back',\n empty: 'No results',\n loading: 'Searching…',\n more_one: '{{count}} more',\n more_other: '{{count}} more',\n typeMore: 'Type at least {{count}} characters to search records.',\n groups: {\n actions: 'Actions',\n commands: 'Commands',\n navigation: 'Navigation',\n tabs: 'Open tabs',\n records: 'Records',\n },\n unavailableScope: 'Not available here: {{reason}}',\n unavailableReach: 'We could not reach this source: {{reason}}',\n openInWorkspaceHint: 'Tab',\n },\n sidebar: {\n expand: 'Expand sidebar',\n collapse: 'Collapse sidebar',\n openMenu: 'Open the menu',\n closeMenu: 'Close the menu',\n menu: 'Navigation menu',\n expandGroup: 'Expand {{group}}',\n collapseGroup: 'Collapse {{group}}',\n },\n session: {\n expiringSoon:\n 'Your session is about to expire. Save your work and sign in again to continue.',\n },\n breadcrumbs: {\n home: 'Home',\n // The `/new` segment, which every domain's create route shares. It was\n // an auth-specific string until one host started serving two domains,\n // at which point a catalog page's breadcrumb read \"New user\".\n new: 'New',\n },\n account: {\n profile: 'Profile',\n security: 'Security',\n sessions: 'Sessions',\n signOut: 'Sign out',\n },\n workspace: {\n copyLink: 'Copy link',\n empty: 'No open tabs. Pick a catalog from the sidebar to start.',\n unsupported: \"This tab can't be opened here.\",\n discardTitle: 'Discard changes',\n discard: 'This tab has unsaved changes. Closing it will lose them.',\n discardConfirm: 'Discard',\n // See the Spanish catalog for why a failure's reason is not a key here.\n pendingLabel: 'Save status',\n savePending_one: 'Saving 1 record…',\n savePending_other: 'Saving {{count}} records…',\n saveFailed: 'The record could not be saved.',\n saveFailedDismiss: 'Dismiss',\n },\n};\n"],"names":["shell","nav","primary","openInWorkspace","openInWorkspaceShort","openInNewTab","openInNewTabShort","screenType","master","operation","wizard","report","commandPalette","open","title","placeholder","newPlaceholder","newLabel","newTitle","back","empty","loading","more_one","more_other","typeMore","groups","actions","commands","navigation","tabs","records","unavailableScope","unavailableReach","openInWorkspaceHint","sidebar","expand","collapse","openMenu","closeMenu","menu","expandGroup","collapseGroup","session","expiringSoon","breadcrumbs","home","new","account","profile","security","sessions","signOut","workspace","copyLink","unsupported","discardTitle","discard","discardConfirm","pendingLabel","savePending_one","savePending_other","saveFailed","saveFailedDismiss"],"mappings":"AAEA,OAAO,MAAMA,QAAiB;IAC5BC,KAAK;QACHC,SAAS;QACTC,iBAAiB;QACjBC,sBAAsB;QACtBC,cAAc;QACdC,mBAAmB;QACnB;;;;KAIC,GACDC,YAAY;YACVC,QAAQ;YACRC,WAAW;YACXC,QAAQ;YACRC,QAAQ;QACV;IACF;IACAC,gBAAgB;QACdC,MAAM;QACNC,OAAO;QACPC,aAAa;QACbC,gBAAgB;QAChBC,UAAU;QACVC,UAAU;QACVC,MAAM;QACNC,OAAO;QACPC,SAAS;QACTC,UAAU;QACVC,YAAY;QACZC,UAAU;QACVC,QAAQ;YACNC,SAAS;YACTC,UAAU;YACVC,YAAY;YACZC,MAAM;YACNC,SAAS;QACX;QACAC,kBAAkB;QAClBC,kBAAkB;QAClBC,qBAAqB;IACvB;IACAC,SAAS;QACPC,QAAQ;QACRC,UAAU;QACVC,UAAU;QACVC,WAAW;QACXC,MAAM;QACNC,aAAa;QACbC,eAAe;IACjB;IACAC,SAAS;QACPC,cACE;IACJ;IACAC,aAAa;QACXC,MAAM;QACN,uEAAuE;QACvE,sEAAsE;QACtE,8DAA8D;QAC9DC,KAAK;IACP;IACAC,SAAS;QACPC,SAAS;QACTC,UAAU;QACVC,UAAU;QACVC,SAAS;IACX;IACAC,WAAW;QACTC,UAAU;QACVjC,OAAO;QACPkC,aAAa;QACbC,cAAc;QACdC,SAAS;QACTC,gBAAgB;QAChB,wEAAwE;QACxEC,cAAc;QACdC,iBAAiB;QACjBC,mBAAmB;QACnBC,YAAY;QACZC,mBAAmB;IACrB;AACF,EAAE"}
|
|
@@ -1,134 +1,5 @@
|
|
|
1
1
|
/** Copy owned by the Next shells — back-office chrome and the tab workspace. */
|
|
2
2
|
export declare const shell: {
|
|
3
|
-
/**
|
|
4
|
-
* System management — the operator surface. Lives in the framework shell so
|
|
5
|
-
* a dedicated management app can mount the same screens later, which is why the
|
|
6
|
-
* copy is here rather than in an app catalog.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* La superficie de existencias — las primeras pantallas de Operaciones del
|
|
10
|
-
* back office.
|
|
11
|
-
*
|
|
12
|
-
* El *nombre del nivel* ("Operaciones") sale de `nav.screenType.operation`;
|
|
13
|
-
* esto nombra el grupo dentro de él.
|
|
14
|
-
*/
|
|
15
|
-
stock: {
|
|
16
|
-
nav: {
|
|
17
|
-
stock: string;
|
|
18
|
-
items: string;
|
|
19
|
-
movements: string;
|
|
20
|
-
reservations: string;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
order: {
|
|
24
|
-
nav: {
|
|
25
|
-
orders: string;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
settlement: {
|
|
29
|
-
nav: {
|
|
30
|
-
settlement: string;
|
|
31
|
-
agreements: string;
|
|
32
|
-
entries: string;
|
|
33
|
-
runs: string;
|
|
34
|
-
payouts: string;
|
|
35
|
-
};
|
|
36
|
-
agreement: {
|
|
37
|
-
channelRatesHint: string;
|
|
38
|
-
channelRateDefault: string;
|
|
39
|
-
noChannelRates: string;
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
sales: {
|
|
43
|
-
nav: {
|
|
44
|
-
sales: string;
|
|
45
|
-
channels: string;
|
|
46
|
-
guided: string;
|
|
47
|
-
counterSale: string;
|
|
48
|
-
};
|
|
49
|
-
counterSale: {
|
|
50
|
-
title: string;
|
|
51
|
-
steps: {
|
|
52
|
-
channel: string;
|
|
53
|
-
lines: string;
|
|
54
|
-
payment: string;
|
|
55
|
-
summary: string;
|
|
56
|
-
};
|
|
57
|
-
channelHint: string;
|
|
58
|
-
linesHint: string;
|
|
59
|
-
addLine: string;
|
|
60
|
-
removeLine: string;
|
|
61
|
-
quantity: string;
|
|
62
|
-
offering: string;
|
|
63
|
-
total: string;
|
|
64
|
-
paymentMethod: string;
|
|
65
|
-
charge: string;
|
|
66
|
-
sold: string;
|
|
67
|
-
soldDetail: string;
|
|
68
|
-
newSale: string;
|
|
69
|
-
emptyLines: string;
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
marketplaceAdmin: {
|
|
73
|
-
nav: {
|
|
74
|
-
catalog: string;
|
|
75
|
-
products: string;
|
|
76
|
-
brands: string;
|
|
77
|
-
categories: string;
|
|
78
|
-
offerings: string;
|
|
79
|
-
offeringPrices: string;
|
|
80
|
-
guided: string;
|
|
81
|
-
};
|
|
82
|
-
/**
|
|
83
|
-
* El asistente de alta de producto. Los nombres de los pasos son de este
|
|
84
|
-
* dominio, no del control — por eso viven acá y no en `controls`.
|
|
85
|
-
*/
|
|
86
|
-
wizard: {
|
|
87
|
-
productSetup: {
|
|
88
|
-
title: string;
|
|
89
|
-
launch: string;
|
|
90
|
-
steps: {
|
|
91
|
-
start: string;
|
|
92
|
-
source: string;
|
|
93
|
-
identity: string;
|
|
94
|
-
classification: string;
|
|
95
|
-
summary: string;
|
|
96
|
-
};
|
|
97
|
-
start: {
|
|
98
|
-
question: string;
|
|
99
|
-
blank: string;
|
|
100
|
-
blankHint: string;
|
|
101
|
-
duplicate: string;
|
|
102
|
-
duplicateHint: string;
|
|
103
|
-
};
|
|
104
|
-
source: {
|
|
105
|
-
prompt: string;
|
|
106
|
-
chosen: string;
|
|
107
|
-
required: string;
|
|
108
|
-
};
|
|
109
|
-
summary: {
|
|
110
|
-
prompt: string;
|
|
111
|
-
origin: string;
|
|
112
|
-
originBlank: string;
|
|
113
|
-
originDuplicate: string;
|
|
114
|
-
empty: string;
|
|
115
|
-
};
|
|
116
|
-
recap: string;
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
systemManagement: {
|
|
121
|
-
nav: {
|
|
122
|
-
section: string;
|
|
123
|
-
configuration: string;
|
|
124
|
-
};
|
|
125
|
-
configuration: {
|
|
126
|
-
title: string;
|
|
127
|
-
description: string;
|
|
128
|
-
secretPlaceholder: string;
|
|
129
|
-
secretHint: string;
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
3
|
nav: {
|
|
133
4
|
primary: string;
|
|
134
5
|
openInWorkspace: string;
|
|
@@ -136,7 +7,7 @@ export declare const shell: {
|
|
|
136
7
|
openInNewTab: string;
|
|
137
8
|
openInNewTabShort: string;
|
|
138
9
|
/**
|
|
139
|
-
* The screen taxonomy
|
|
10
|
+
* The screen taxonomy — the sidebar's top tier, above the
|
|
140
11
|
* domain. `Definiciones` rather than `Maestros` because the ERP term needs
|
|
141
12
|
* the ERP background to parse, and rather than `Catálogos` or `Referencias`
|
|
142
13
|
* because both already mean something else here: the product catalog, and a
|
|
@@ -150,7 +21,7 @@ export declare const shell: {
|
|
|
150
21
|
};
|
|
151
22
|
};
|
|
152
23
|
/**
|
|
153
|
-
* La paleta de comandos
|
|
24
|
+
* La paleta de comandos. Vive en `shell:` y no en `controls:`
|
|
154
25
|
* porque el control es agnóstico —recibe copia ya resuelta— y quien la
|
|
155
26
|
* arma es el back office.
|
|
156
27
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../../../src/lib/i18n/catalog/es.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,KAAK
|
|
1
|
+
{"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../../../src/lib/i18n/catalog/es.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,KAAK;;;;;;;QAOd;;;;;;WAMG;;;;;;;;IAQL;;;;OAIG;;QAED,4EAA4E;;QAE5E,qDAAqD;;;;;QAKrD,4EAA4E;;;;;;;QAO5E,2EAA2E;;;;;;;;;QAS3E;;;;WAIG;;;QAGH,uEAAuE;;;;;;;;;;;;IAezE;;;;;OAKG;;;;;;;;IAYH;;;;OAIG;;;;;;;;;;;;;;;;;;;;CAkCJ,CAAC"}
|