@elytracms/studio 0.0.98 → 0.0.99

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.
Files changed (91) hide show
  1. package/convex/ingest.test.ts +84 -7
  2. package/convex/ingest.ts +110 -31
  3. package/convex/path-segment-refusal.test.ts +201 -0
  4. package/convex/records-column-transform.convex.test.ts +147 -0
  5. package/convex/schema.ts +1 -1
  6. package/dist/addon.d.ts +5 -3
  7. package/dist/addon.js +4 -3
  8. package/dist/addon.js.map +1 -1
  9. package/dist/components/app-views/default-modules.d.ts +10 -3
  10. package/dist/components/app-views/default-modules.js +5 -1
  11. package/dist/components/app-views/default-modules.js.map +1 -1
  12. package/dist/components/app-views/module.d.ts +15 -8
  13. package/dist/components/app-views/module.js.map +1 -1
  14. package/dist/components/builder/reuse-context.d.ts +29 -1
  15. package/dist/components/builder/reuse-context.js +58 -12
  16. package/dist/components/builder/reuse-context.js.map +1 -1
  17. package/dist/components/cms/cms-store.d.ts +3 -2
  18. package/dist/components/cms/cms-store.js +8 -2
  19. package/dist/components/cms/cms-store.js.map +1 -1
  20. package/dist/components/cms/field-control.js +15 -5
  21. package/dist/components/cms/field-control.js.map +1 -1
  22. package/dist/components/cms/use-document-draft.d.ts +4 -1
  23. package/dist/components/cms/use-document-draft.js +4 -1
  24. package/dist/components/cms/use-document-draft.js.map +1 -1
  25. package/dist/components/cms/use-publish-controller.d.ts +7 -0
  26. package/dist/components/cms/use-publish-controller.js +17 -3
  27. package/dist/components/cms/use-publish-controller.js.map +1 -1
  28. package/dist/components/cms/versions-pane.js +8 -5
  29. package/dist/components/cms/versions-pane.js.map +1 -1
  30. package/dist/components/delivery-health-banner.d.ts +34 -16
  31. package/dist/components/delivery-health-banner.js +59 -27
  32. package/dist/components/delivery-health-banner.js.map +1 -1
  33. package/dist/components/issue-severity.d.ts +3 -2
  34. package/dist/components/issue-severity.js.map +1 -1
  35. package/dist/components/nav-issues-registry.d.ts +39 -24
  36. package/dist/components/nav-issues-registry.js +50 -21
  37. package/dist/components/nav-issues-registry.js.map +1 -1
  38. package/dist/components/nav-issues.d.ts +23 -50
  39. package/dist/components/nav-issues.js +31 -59
  40. package/dist/components/nav-issues.js.map +1 -1
  41. package/dist/components/notifications/notice-center.d.ts +61 -0
  42. package/dist/components/notifications/notice-center.js +119 -0
  43. package/dist/components/notifications/notice-center.js.map +1 -0
  44. package/dist/components/notifications/notice-model.d.ts +137 -0
  45. package/dist/components/notifications/notice-model.js +107 -0
  46. package/dist/components/notifications/notice-model.js.map +1 -0
  47. package/dist/components/notifications/outcome-notice.d.ts +16 -0
  48. package/dist/components/notifications/outcome-notice.js +34 -0
  49. package/dist/components/notifications/outcome-notice.js.map +1 -0
  50. package/dist/components/notifications/shell-findings.d.ts +36 -0
  51. package/dist/components/notifications/shell-findings.js +140 -0
  52. package/dist/components/notifications/shell-findings.js.map +1 -0
  53. package/dist/components/rail-view-source.js +5 -0
  54. package/dist/components/rail-view-source.js.map +1 -1
  55. package/dist/components/records/record-rail.d.ts +10 -2
  56. package/dist/components/records/record-rail.js +74 -4
  57. package/dist/components/records/record-rail.js.map +1 -1
  58. package/dist/components/section-nav.d.ts +15 -3
  59. package/dist/components/section-nav.js +39 -147
  60. package/dist/components/section-nav.js.map +1 -1
  61. package/dist/components/studio-project-screen.js.map +1 -1
  62. package/dist/components/studio-shell.js +8 -3
  63. package/dist/components/studio-shell.js.map +1 -1
  64. package/dist/components/use-narrow-viewport.d.ts +11 -0
  65. package/dist/components/use-narrow-viewport.js +25 -0
  66. package/dist/components/use-narrow-viewport.js.map +1 -0
  67. package/dist/components/users/role-policy-drift-issues.d.ts +14 -7
  68. package/dist/components/users/role-policy-drift-issues.js +17 -8
  69. package/dist/components/users/role-policy-drift-issues.js.map +1 -1
  70. package/dist/lib/collection-view/collection-view-source.d.ts +4 -2
  71. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  72. package/dist/lib/engine-version.d.ts +1 -1
  73. package/dist/lib/engine-version.js +1 -1
  74. package/dist/lib/engine-version.js.map +1 -1
  75. package/dist/lib/i18n/messages.de.js +21 -3
  76. package/dist/lib/i18n/messages.de.js.map +1 -1
  77. package/dist/lib/i18n/messages.en.d.ts +19 -2
  78. package/dist/lib/i18n/messages.en.js +24 -4
  79. package/dist/lib/i18n/messages.en.js.map +1 -1
  80. package/dist/lib/notices/toast-store.d.ts +61 -0
  81. package/dist/lib/notices/toast-store.js +116 -0
  82. package/dist/lib/notices/toast-store.js.map +1 -0
  83. package/dist/lib/operations/cms-persistence.d.ts +24 -4
  84. package/dist/lib/operations/cms-persistence.js +417 -80
  85. package/dist/lib/operations/cms-persistence.js.map +1 -1
  86. package/dist/lib/persistence/convex-adapter.js +61 -30
  87. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  88. package/dist/ui/toast.d.ts +24 -16
  89. package/dist/ui/toast.js +23 -32
  90. package/dist/ui/toast.js.map +1 -1
  91. package/package.json +3 -3
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useEffect, useMemo, useSyncExternalStore } from 'react';
3
- /** The shell-scoped store of contributed findings, keyed by source. */
2
+ import { createContext, useContext, useEffect, useId, useMemo, useSyncExternalStore } from 'react';
3
+ import { translate } from '../lib/i18n/index.js';
4
+ import { isReservedFindingSource } from './notifications/notice-model.js';
5
+ /** The shell-scoped store of contributed findings, keyed by PUBLISHER. */
4
6
  export class NavIssuesRegistry {
5
- bySource = new Map();
7
+ byPublisher = new Map();
6
8
  listeners = new Set();
7
9
  snapshot = [];
8
10
  subscribe = (listener) => {
@@ -12,23 +14,48 @@ export class NavIssuesRegistry {
12
14
  };
13
15
  };
14
16
  getSnapshot = () => this.snapshot;
15
- /** Publish (or replace) one source's findings; an empty list removes the source. */
16
- publish(source, issues) {
17
- if (issues.length === 0)
18
- this.bySource.delete(source);
19
- else
20
- this.bySource.set(source, issues);
17
+ /**
18
+ * Publish (or replace) ONE publisher's findings under `source`; an empty list removes the
19
+ * publisher. `publisher` is the registry-assigned identity ({@link useNavIssues} passes its
20
+ * `useId`), so another publisher naming the same source is untouched.
21
+ */
22
+ publish(publisher, source, issues) {
23
+ if (issues.length === 0) {
24
+ if (!this.byPublisher.delete(publisher))
25
+ return;
26
+ }
27
+ else {
28
+ this.byPublisher.set(publisher, { source, issues });
29
+ }
21
30
  this.recompute();
22
31
  }
23
- retract(source) {
24
- if (this.bySource.delete(source))
32
+ retract(publisher) {
33
+ if (this.byPublisher.delete(publisher))
25
34
  this.recompute();
26
35
  }
27
36
  recompute() {
28
37
  const items = [];
29
- for (const [source, issues] of this.bySource) {
38
+ for (const [publisher, { source, issues }] of this.byPublisher) {
30
39
  for (const issue of issues) {
31
- items.push({ key: `contributed:${source}:${issue.key}`, message: issue.message, severity: issue.severity ?? 'warning' });
40
+ items.push({
41
+ id: `${publisher}:${issue.key}`,
42
+ source,
43
+ key: issue.key,
44
+ message: issue.message,
45
+ severity: issue.severity ?? 'warning',
46
+ ...(issue.title !== undefined ? { title: issue.title } : {}),
47
+ ...(issue.location !== undefined ? { location: issue.location } : {}),
48
+ });
49
+ }
50
+ if (isReservedFindingSource(source)) {
51
+ items.push({
52
+ id: `${publisher}:reserved-source`,
53
+ source: 'engine:registry',
54
+ key: `reserved-source:${source}`,
55
+ message: translate('shell.notices.reservedSource', { source }),
56
+ severity: 'error',
57
+ location: { kind: 'config', declaredIn: 'studio' },
58
+ });
32
59
  }
33
60
  }
34
61
  this.snapshot = items;
@@ -37,7 +64,7 @@ export class NavIssuesRegistry {
37
64
  }
38
65
  }
39
66
  const NavIssuesContext = createContext(null);
40
- /** Mounted once by the studio project screen, around the shell (rail + content). */
67
+ /** Mounted once by the studio project screen, around the shell (header, rail + content). */
41
68
  export function NavIssuesProvider({ children }) {
42
69
  const registry = useMemo(() => new NavIssuesRegistry(), []);
43
70
  return _jsx(NavIssuesContext.Provider, { value: registry, children: children });
@@ -50,18 +77,20 @@ function useRegistry(caller) {
50
77
  return registry;
51
78
  }
52
79
  /**
53
- * Publish findings to the rail foot while this component is mounted. `source` names the
54
- * publisher (`'forms'`, `'analytics:beacon'`); pass the same `issues` array identity
55
- * between renders (memoize it) or the foot re-renders on every render.
80
+ * Publish findings to the shell's issues tray while this component is mounted. `source`
81
+ * names the publisher's kind (`'forms'`, `'analytics:beacon'`) — it groups and orders, it is
82
+ * not an address: each mounted call is its own publisher. Pass the same `issues` array
83
+ * identity between renders (memoize it) or the tray re-renders on every render.
56
84
  */
57
85
  export function useNavIssues(source, issues) {
58
86
  const registry = useRegistry('useNavIssues');
87
+ const publisher = useId();
59
88
  useEffect(() => {
60
- registry.publish(source, issues);
61
- }, [registry, source, issues]);
62
- useEffect(() => () => registry.retract(source), [registry, source]);
89
+ registry.publish(publisher, source, issues);
90
+ }, [registry, publisher, source, issues]);
91
+ useEffect(() => () => registry.retract(publisher), [registry, publisher]);
63
92
  }
64
- /** The contributed findings, for the rail foot. */
93
+ /** The contributed findings, for the issues tray. */
65
94
  export function useContributedNavIssues() {
66
95
  const registry = useRegistry('useContributedNavIssues');
67
96
  return useSyncExternalStore(registry.subscribe, registry.getSnapshot, registry.getSnapshot);
@@ -1 +1 @@
1
- {"version":3,"file":"nav-issues-registry.js","sourceRoot":"","sources":["../../src/components/nav-issues-registry.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useContext, useEffect, useMemo, useSyncExternalStore } from 'react'\nimport type { ReactNode } from 'react'\nimport type { IssueSeverity } from './issue-severity'\nimport type { NavIssueItem } from './nav-issues'\n\n/**\n * EC-616 — the rail foot's CONTRIBUTION seam (`useNavIssues`, ruling § 6b item 1).\n *\n * The issues foot (`SectionNav`) is on every studio surface, so it is where a finding the\n * developer must not miss belongs — but its sources were a fixed list (config, nav,\n * renderers, reuse, routes, orphans), and a custom view or an addon had no way in. Now any\n * component under the studio shell publishes findings with {@link useNavIssues}; the foot\n * renders them beside its own. While the publishing component is mounted its findings\n * show; on unmount they go. A rail item that must ALWAYS report (not only while its view\n * is open) declares `useIssues` on its view (`nav.view({ useIssues })` /\n * `nav.rail(kind, { useIssues })`) — the rail mounts that hook for it.\n *\n * A finding is plain data: a stable `key` (unique within its source), the localized\n * `message`, and a `severity` (`'error' | 'warning'`, default `'warning'`).\n */\n\n/** One finding a view or addon puts in the rail foot. */\nexport interface NavIssue {\n /** Stable within its source (re-publishing the same key replaces, never duplicates). */\n key: string\n /** The localized message, shown as written. */\n message: string\n /** Default `'warning'`. */\n severity?: IssueSeverity\n}\n\ntype Listener = () => void\n\n/** The shell-scoped store of contributed findings, keyed by source. */\nexport class NavIssuesRegistry {\n private readonly bySource = new Map<string, readonly NavIssue[]>()\n private readonly listeners = new Set<Listener>()\n private snapshot: readonly NavIssueItem[] = []\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getSnapshot = (): readonly NavIssueItem[] => this.snapshot\n\n /** Publish (or replace) one source's findings; an empty list removes the source. */\n publish(source: string, issues: readonly NavIssue[]): void {\n if (issues.length === 0) this.bySource.delete(source)\n else this.bySource.set(source, issues)\n this.recompute()\n }\n\n retract(source: string): void {\n if (this.bySource.delete(source)) this.recompute()\n }\n\n private recompute(): void {\n const items: NavIssueItem[] = []\n for (const [source, issues] of this.bySource) {\n for (const issue of issues) {\n items.push({ key: `contributed:${source}:${issue.key}`, message: issue.message, severity: issue.severity ?? 'warning' })\n }\n }\n this.snapshot = items\n for (const listener of [...this.listeners]) listener()\n }\n}\n\nconst NavIssuesContext = createContext<NavIssuesRegistry | null>(null)\n\n/** Mounted once by the studio project screen, around the shell (rail + content). */\nexport function NavIssuesProvider({ children }: { children: ReactNode }) {\n const registry = useMemo(() => new NavIssuesRegistry(), [])\n return <NavIssuesContext.Provider value={registry}>{children}</NavIssuesContext.Provider>\n}\n\nfunction useRegistry(caller: string): NavIssuesRegistry {\n const registry = useContext(NavIssuesContext)\n if (!registry) {\n throw new Error(`${caller} must be used inside the studio shell (no NavIssuesProvider above this component).`)\n }\n return registry\n}\n\n/**\n * Publish findings to the rail foot while this component is mounted. `source` names the\n * publisher (`'forms'`, `'analytics:beacon'`); pass the same `issues` array identity\n * between renders (memoize it) or the foot re-renders on every render.\n */\nexport function useNavIssues(source: string, issues: readonly NavIssue[]): void {\n const registry = useRegistry('useNavIssues')\n useEffect(() => {\n registry.publish(source, issues)\n }, [registry, source, issues])\n useEffect(() => () => registry.retract(source), [registry, source])\n}\n\n/** The contributed findings, for the rail foot. */\nexport function useContributedNavIssues(): readonly NavIssueItem[] {\n const registry = useRegistry('useContributedNavIssues')\n return useSyncExternalStore(registry.subscribe, registry.getSnapshot, registry.getSnapshot)\n}\n"]}
1
+ {"version":3,"file":"nav-issues-registry.js","sourceRoot":"","sources":["../../src/components/nav-issues-registry.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useContext, useEffect, useId, useMemo, useSyncExternalStore } from 'react'\nimport type { ReactNode } from 'react'\nimport { translate } from '../lib/i18n'\nimport type { IssueSeverity } from './issue-severity'\nimport { isReservedFindingSource } from './notifications/notice-model'\nimport type { Finding, NoticeLocation } from './notifications/notice-model'\n\n/**\n * EC-616 — the studio's CONTRIBUTION seam for findings (`useNavIssues`, ruling § 6b item 1);\n * since EC-690 the registry of the ONE notification-and-issue surface: everything it holds\n * is listed in the shell header's issues tray (the badge), beside the engine's own findings.\n *\n * Any component under the studio shell publishes findings with {@link useNavIssues}. While\n * the publishing component is mounted its findings are listed; on unmount they go. A rail\n * item that must ALWAYS report (not only while its view is open) declares `useIssues` on its\n * view (`nav.view({ useIssues })` / `nav.rail(kind, { useIssues })`) — the shell mounts that\n * hook for it.\n *\n * A finding is plain data: a stable `key` (unique within its source), the localized\n * `message`, a `severity` (`'error' | 'warning'`, default `'warning'`), and since EC-690 an\n * optional short `title` and a `location` (where the tray's \"Go to\" leads, or the file it names).\n *\n * EC-690 (critic point 1): every publisher has its OWN identity, assigned by the registry\n * (`useId`), so two mounted publishers that name the same source both stay listed — one can\n * never overwrite or retract the other's findings. The engine's sources (`config`, `routes`,\n * …, and the `engine:` prefix) are reserved: a contributor using one is still listed, and the\n * misuse is reported as a finding of its own.\n */\n\n/** One finding a view or addon publishes. */\nexport interface NavIssue {\n /** Stable within its source (re-publishing the same key replaces, never duplicates). */\n key: string\n /** The localized message, shown as written. */\n message: string\n /** Default `'warning'`. */\n severity?: IssueSeverity\n /** EC-690: a short localized title the tray leads the row with (e.g. \"Invalid redirect\"). */\n title?: string\n /** EC-690: where the finding is — the tray's \"Go to\" (or the file it names). */\n location?: NoticeLocation\n}\n\ntype Listener = () => void\n\ninterface Publication {\n source: string\n issues: readonly NavIssue[]\n}\n\n/** The shell-scoped store of contributed findings, keyed by PUBLISHER. */\nexport class NavIssuesRegistry {\n private readonly byPublisher = new Map<string, Publication>()\n private readonly listeners = new Set<Listener>()\n private snapshot: readonly Finding[] = []\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n getSnapshot = (): readonly Finding[] => this.snapshot\n\n /**\n * Publish (or replace) ONE publisher's findings under `source`; an empty list removes the\n * publisher. `publisher` is the registry-assigned identity ({@link useNavIssues} passes its\n * `useId`), so another publisher naming the same source is untouched.\n */\n publish(publisher: string, source: string, issues: readonly NavIssue[]): void {\n if (issues.length === 0) {\n if (!this.byPublisher.delete(publisher)) return\n } else {\n this.byPublisher.set(publisher, { source, issues })\n }\n this.recompute()\n }\n\n retract(publisher: string): void {\n if (this.byPublisher.delete(publisher)) this.recompute()\n }\n\n private recompute(): void {\n const items: Finding[] = []\n for (const [publisher, { source, issues }] of this.byPublisher) {\n for (const issue of issues) {\n items.push({\n id: `${publisher}:${issue.key}`,\n source,\n key: issue.key,\n message: issue.message,\n severity: issue.severity ?? 'warning',\n ...(issue.title !== undefined ? { title: issue.title } : {}),\n ...(issue.location !== undefined ? { location: issue.location } : {}),\n })\n }\n if (isReservedFindingSource(source)) {\n items.push({\n id: `${publisher}:reserved-source`,\n source: 'engine:registry',\n key: `reserved-source:${source}`,\n message: translate('shell.notices.reservedSource', { source }),\n severity: 'error',\n location: { kind: 'config', declaredIn: 'studio' },\n })\n }\n }\n this.snapshot = items\n for (const listener of [...this.listeners]) listener()\n }\n}\n\nconst NavIssuesContext = createContext<NavIssuesRegistry | null>(null)\n\n/** Mounted once by the studio project screen, around the shell (header, rail + content). */\nexport function NavIssuesProvider({ children }: { children: ReactNode }) {\n const registry = useMemo(() => new NavIssuesRegistry(), [])\n return <NavIssuesContext.Provider value={registry}>{children}</NavIssuesContext.Provider>\n}\n\nfunction useRegistry(caller: string): NavIssuesRegistry {\n const registry = useContext(NavIssuesContext)\n if (!registry) {\n throw new Error(`${caller} must be used inside the studio shell (no NavIssuesProvider above this component).`)\n }\n return registry\n}\n\n/**\n * Publish findings to the shell's issues tray while this component is mounted. `source`\n * names the publisher's kind (`'forms'`, `'analytics:beacon'`) — it groups and orders, it is\n * not an address: each mounted call is its own publisher. Pass the same `issues` array\n * identity between renders (memoize it) or the tray re-renders on every render.\n */\nexport function useNavIssues(source: string, issues: readonly NavIssue[]): void {\n const registry = useRegistry('useNavIssues')\n const publisher = useId()\n useEffect(() => {\n registry.publish(publisher, source, issues)\n }, [registry, publisher, source, issues])\n useEffect(() => () => registry.retract(publisher), [registry, publisher])\n}\n\n/** The contributed findings, for the issues tray. */\nexport function useContributedNavIssues(): readonly Finding[] {\n const registry = useRegistry('useContributedNavIssues')\n return useSyncExternalStore(registry.subscribe, registry.getSnapshot, registry.getSnapshot)\n}\n"]}
@@ -1,62 +1,55 @@
1
1
  import type { CmsValidationIssue, OrphanedCollection } from '@elytracms/core/cms-core';
2
2
  import type { CustomizationIssue } from '@elytracms/core/studio-core';
3
3
  import type { IssueSeverity } from './issue-severity.js';
4
+ import type { NavIssue } from './nav-issues-registry.js';
4
5
  /**
5
- * What the rail foot needs to render one finding: a stable key, a message, and the
6
- * finding's severity. The least common shape across its contributing sources —
7
- * customization issues (`CustomizationIssue`) and URL-map validation
8
- * (`CmsValidationIssue`, EC-469) — so a new source joins by mapping to this, not by
9
- * pretending to be a customization issue.
6
+ * The engine's own project-level findings, mapped to the one shape the issues tray lists
7
+ * (EC-690; before it, the rail foot's cards). The least common shape across the sources —
8
+ * customization issues (`CustomizationIssue`) and URL-map validation (`CmsValidationIssue`,
9
+ * EC-469) — so a new source joins by mapping to this, not by pretending to be a
10
+ * customization issue.
10
11
  *
11
12
  * `severity` was added by EC-493: both mappers used to drop it and the widget painted
12
- * every finding amber, which stopped being a style nit once EC-488 re-derived
13
- * `shadowedMountSpaces` from `error` to `warning` ON THE REASONING that the two mean
14
- * different things, and EC-482 shipped one producer (`hierarchyMountConflicts`)
15
- * emitting both into this very list.
13
+ * every finding amber. EC-690 added `title` (the short label a row leads with) and
14
+ * `location` (where "Go to" leads, or the file the row names).
16
15
  */
17
- export interface NavIssueItem {
18
- key: string;
19
- message: string;
16
+ export type NavIssueItem = Required<Pick<NavIssue, 'key' | 'message'>> & {
20
17
  severity: IssueSeverity;
21
- }
18
+ } & Pick<NavIssue, 'title' | 'location'>;
22
19
  /**
23
- * Customization issues, as rail-foot items — localized (EC-478). The producers
20
+ * Customization issues, as findings — localized (EC-478). The producers
24
21
  * (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a
25
22
  * catalog key + identifier values rather than a sentence, because the EC-236 seam
26
23
  * lives in `packages/core` and may not import the studio catalog;
27
- * {@link customizationIssueMessage} resolves them here, so this list renders wholly in
28
- * the active locale instead of half of it.
24
+ * {@link customizationIssueMessage} resolves them here.
29
25
  *
30
- * EC-493: `severity` rides along. It used to be dropped here, which is half of why an
31
- * error and a warning were indistinguishable at the foot — `CustomizationIssue` has
32
- * carried the field since EC-236, the mapper simply did not read it.
26
+ * EC-690: located where the developer fixes them — the project config's own issues
27
+ * (EC-479, point `project`) in `elytra.config.ts`, every other point (the rail, field
28
+ * renderers, app views, reuse, the AI endpoint) in the studio config (`defineStudio`).
33
29
  */
34
30
  export declare function customizationItems(issues: readonly CustomizationIssue[]): NavIssueItem[];
35
31
  /**
36
- * URL-map validation issues (EC-469), as rail-foot items.
32
+ * URL-map validation issues (EC-469), as findings.
37
33
  *
38
- * Copy follows the issues-pane convention (EC-192): the localized plain-language label,
39
- * then the detail — both localized since EC-487, which gave `CmsValidationIssue` the
40
- * same catalog-reference carriage EC-478 gave `CustomizationIssue`.
34
+ * EC-690: the localized plain-language label (EC-192) is the row's `title`, the detail its
35
+ * `message` — the two used to be joined into one sentence. A redirect finding is located at
36
+ * its row in the redirects rail (by record kind), a route finding at `elytra.config.ts`.
41
37
  *
42
- * EC-493: `severity` rides along, the other half of the fix. It matters most here:
43
- * EC-482's `hierarchyMountConflicts` emits `error` for two mounts on the same path and
44
- * `warning` for a nested pair FROM ONE FUNCTION, and EC-488 re-derived
45
- * `shadowedMountSpaces` to `warning` precisely because the fall-through made it
46
- * conditional. Both land in this list.
38
+ * EC-493: `severity` rides along — EC-482's `hierarchyMountConflicts` emits `error` and
39
+ * `warning` from one function, and both land here.
47
40
  */
48
41
  export declare function routeIssueItems(issues: readonly CmsValidationIssue[]): NavIssueItem[];
49
42
  /** The stable key of the one orphaned-collections finding (EC-606). */
50
43
  export declare const ORPHANED_COLLECTIONS_ISSUE_KEY = "orphaned-collections";
51
44
  /**
52
45
  * EC-606 — stored documents whose collection the project does not declare, as ONE
53
- * rail-foot item (or none).
46
+ * finding (or none).
54
47
  *
55
48
  * Session hydration keeps only the declared collections' documents and counts the
56
49
  * rest (`HydratedCms.orphans`, via core's `orphanedCollections`). Before EC-606 those
57
50
  * rows were dropped with no trace, so renaming a collection id in config made its
58
51
  * documents vanish from the studio. One item for the whole list: it is one condition
59
- * with one remedy, and N cards would crowd out the rail.
52
+ * with one remedy, and N rows would crowd out the rest.
60
53
  *
61
54
  * `warning`, not `error`: nothing is lost (the rows are stored, untouched) and nothing
62
55
  * the editor does is blocked. The message names only one remedy, declaring the
@@ -66,23 +59,3 @@ export declare const ORPHANED_COLLECTIONS_ISSUE_KEY = "orphaned-collections";
66
59
  * that has one: those rows are still on the public delivery snapshot.
67
60
  */
68
61
  export declare function orphanedCollectionItems(orphans: readonly OrphanedCollection[]): NavIssueItem[];
69
- /**
70
- * Visible project-level issues at the rail foot — the seam's "never silent" (EC-236,
71
- * extended by EC-467's reuse issues, EC-469's URL-map issues, EC-479's config
72
- * issues and EC-606's orphaned collections).
73
- *
74
- * EC-493: an `error` and a `warning` render distinguishably here, in the treatment the
75
- * document Issues pane already uses. Each finding is its own tinted card (as in the
76
- * pane) rather than one shared amber box, because a shared box cannot express a mixed
77
- * list; collapsed, the single glyph follows {@link highestIssueSeverity}. EC-507 moved
78
- * that treatment to `components/issue-severity.ts`, the one home all three surfaces
79
- * import from.
80
- *
81
- * `data-nav-issue-severity` is on each card (and on the collapsed glyph) as the
82
- * machine-readable form of the same distinction — what the smoke test pins, and what an
83
- * agent inspecting the DOM reads without decoding Tailwind classes.
84
- */
85
- export declare function NavIssues({ issues, collapsed }: {
86
- issues: NavIssueItem[];
87
- collapsed: boolean;
88
- }): import("react").JSX.Element;
@@ -1,60 +1,63 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cn } from '../lib/utils.js';
3
1
  import { translate } from '../lib/i18n/index.js';
4
- import { PROJECT_CONFIG_FILE } from '../lib/workspace/index.js';
2
+ import { PROJECT_CONFIG_FILE, PROJECT_CONFIG_POINT } from '../lib/workspace/index.js';
5
3
  import { cmsIssueLabel, cmsValidationIssueMessage, customizationIssueMessage, } from '../lib/persona/labels.js';
6
- import { Row, Stack, Text } from '@elytracms/studio/ui';
7
- import { highestIssueSeverity, issueSeverityIcon, issueSeverityStyle } from './issue-severity.js';
4
+ import { routeIssueLocation } from './notifications/notice-model.js';
8
5
  /**
9
- * Customization issues, as rail-foot items — localized (EC-478). The producers
6
+ * Customization issues, as findings — localized (EC-478). The producers
10
7
  * (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a
11
8
  * catalog key + identifier values rather than a sentence, because the EC-236 seam
12
9
  * lives in `packages/core` and may not import the studio catalog;
13
- * {@link customizationIssueMessage} resolves them here, so this list renders wholly in
14
- * the active locale instead of half of it.
10
+ * {@link customizationIssueMessage} resolves them here.
15
11
  *
16
- * EC-493: `severity` rides along. It used to be dropped here, which is half of why an
17
- * error and a warning were indistinguishable at the foot — `CustomizationIssue` has
18
- * carried the field since EC-236, the mapper simply did not read it.
12
+ * EC-690: located where the developer fixes them — the project config's own issues
13
+ * (EC-479, point `project`) in `elytra.config.ts`, every other point (the rail, field
14
+ * renderers, app views, reuse, the AI endpoint) in the studio config (`defineStudio`).
19
15
  */
20
16
  export function customizationItems(issues) {
21
17
  return issues.map((issue, index) => ({
22
18
  key: `${issue.point}:${issue.id ?? index}`,
23
19
  message: customizationIssueMessage(issue),
24
20
  severity: issue.severity,
21
+ location: {
22
+ kind: 'config',
23
+ declaredIn: issue.point === PROJECT_CONFIG_POINT ? 'project' : 'studio',
24
+ ...(issue.id !== undefined ? { entry: issue.id } : {}),
25
+ },
25
26
  }));
26
27
  }
27
28
  /**
28
- * URL-map validation issues (EC-469), as rail-foot items.
29
+ * URL-map validation issues (EC-469), as findings.
29
30
  *
30
- * Copy follows the issues-pane convention (EC-192): the localized plain-language label,
31
- * then the detail — both localized since EC-487, which gave `CmsValidationIssue` the
32
- * same catalog-reference carriage EC-478 gave `CustomizationIssue`.
31
+ * EC-690: the localized plain-language label (EC-192) is the row's `title`, the detail its
32
+ * `message` — the two used to be joined into one sentence. A redirect finding is located at
33
+ * its row in the redirects rail (by record kind), a route finding at `elytra.config.ts`.
33
34
  *
34
- * EC-493: `severity` rides along, the other half of the fix. It matters most here:
35
- * EC-482's `hierarchyMountConflicts` emits `error` for two mounts on the same path and
36
- * `warning` for a nested pair FROM ONE FUNCTION, and EC-488 re-derived
37
- * `shadowedMountSpaces` to `warning` precisely because the fall-through made it
38
- * conditional. Both land in this list.
35
+ * EC-493: `severity` rides along — EC-482's `hierarchyMountConflicts` emits `error` and
36
+ * `warning` from one function, and both land here.
39
37
  */
40
38
  export function routeIssueItems(issues) {
41
- return issues.map((issue, index) => ({
42
- key: `route-issue:${issue.code}:${issue.path.join('/') || index}`,
43
- message: `${cmsIssueLabel(issue.code, issue.severity)} — ${cmsValidationIssueMessage(issue)}`,
44
- severity: issue.severity,
45
- }));
39
+ return issues.map((issue, index) => {
40
+ const location = routeIssueLocation(issue);
41
+ return {
42
+ key: `route-issue:${issue.code}:${issue.path.join('/') || index}`,
43
+ title: cmsIssueLabel(issue.code, issue.severity),
44
+ message: cmsValidationIssueMessage(issue),
45
+ severity: issue.severity,
46
+ ...(location !== undefined ? { location } : {}),
47
+ };
48
+ });
46
49
  }
47
50
  /** The stable key of the one orphaned-collections finding (EC-606). */
48
51
  export const ORPHANED_COLLECTIONS_ISSUE_KEY = 'orphaned-collections';
49
52
  /**
50
53
  * EC-606 — stored documents whose collection the project does not declare, as ONE
51
- * rail-foot item (or none).
54
+ * finding (or none).
52
55
  *
53
56
  * Session hydration keeps only the declared collections' documents and counts the
54
57
  * rest (`HydratedCms.orphans`, via core's `orphanedCollections`). Before EC-606 those
55
58
  * rows were dropped with no trace, so renaming a collection id in config made its
56
59
  * documents vanish from the studio. One item for the whole list: it is one condition
57
- * with one remedy, and N cards would crowd out the rail.
60
+ * with one remedy, and N rows would crowd out the rest.
58
61
  *
59
62
  * `warning`, not `error`: nothing is lost (the rows are stored, untouched) and nothing
60
63
  * the editor does is blocked. The message names only one remedy, declaring the
@@ -76,39 +79,8 @@ export function orphanedCollectionItems(orphans) {
76
79
  key: ORPHANED_COLLECTIONS_ISSUE_KEY,
77
80
  message: translate('shell.nav.orphanedCollections', { collections, file: PROJECT_CONFIG_FILE }),
78
81
  severity: 'warning',
82
+ location: { kind: 'config', declaredIn: 'project' },
79
83
  },
80
84
  ];
81
85
  }
82
- /**
83
- * Visible project-level issues at the rail foot — the seam's "never silent" (EC-236,
84
- * extended by EC-467's reuse issues, EC-469's URL-map issues, EC-479's config
85
- * issues and EC-606's orphaned collections).
86
- *
87
- * EC-493: an `error` and a `warning` render distinguishably here, in the treatment the
88
- * document Issues pane already uses. Each finding is its own tinted card (as in the
89
- * pane) rather than one shared amber box, because a shared box cannot express a mixed
90
- * list; collapsed, the single glyph follows {@link highestIssueSeverity}. EC-507 moved
91
- * that treatment to `components/issue-severity.ts`, the one home all three surfaces
92
- * import from.
93
- *
94
- * `data-nav-issue-severity` is on each card (and on the collapsed glyph) as the
95
- * machine-readable form of the same distinction — what the smoke test pins, and what an
96
- * agent inspecting the DOM reads without decoding Tailwind classes.
97
- */
98
- export function NavIssues({ issues, collapsed }) {
99
- if (collapsed) {
100
- const severity = highestIssueSeverity(issues);
101
- const Icon = issueSeverityIcon(severity);
102
- return (_jsx("div", { className: cn('grid h-9 place-items-center', issueSeverityStyle(severity).text), "data-nav-issue-severity": severity, title: issues.map((issue) => issue.message).join('\n'), children: _jsx(Icon, { className: "size-4", "aria-label": translate('shell.nav.issuesCount', { count: issues.length }) }) }));
103
- }
104
- return (_jsx(Stack, { gap: "1", className: "mb-1", children: issues.map((issue) => (_jsx(NavIssueCard, { issue: issue }, issue.key))) }));
105
- }
106
- /** One finding at the rail foot, tinted + glyphed by its severity. */
107
- function NavIssueCard({ issue }) {
108
- const style = issueSeverityStyle(issue.severity);
109
- const Icon = issueSeverityIcon(issue.severity);
110
- return (_jsxs(Row, { gap: "1", align: "start",
111
- // `gap-1.5` (the pre-EC-493 icon/text spacing) merged over the primitive's `gap-1`.
112
- className: cn('gap-1.5 rounded-md border px-2 py-1.5', style.card, style.text), "data-nav-issue-severity": issue.severity, title: issue.message, children: [_jsx(Icon, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "min-w-0 leading-snug", children: issue.message })] }));
113
- }
114
86
  //# sourceMappingURL=nav-issues.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nav-issues.js","sourceRoot":"","sources":["../../src/components/nav-issues.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { CmsValidationIssue, OrphanedCollection } from '@elytracms/core/cms-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '../lib/utils'\nimport { translate } from '../lib/i18n'\nimport { PROJECT_CONFIG_FILE } from '../lib/workspace'\nimport {\n cmsIssueLabel,\n cmsValidationIssueMessage,\n customizationIssueMessage,\n} from '../lib/persona/labels'\nimport { Row, Stack, Text } from '@elytracms/studio/ui'\nimport { highestIssueSeverity, issueSeverityIcon, issueSeverityStyle } from './issue-severity'\nimport type { IssueSeverity } from './issue-severity'\n\n/**\n * What the rail foot needs to render one finding: a stable key, a message, and the\n * finding's severity. The least common shape across its contributing sources —\n * customization issues (`CustomizationIssue`) and URL-map validation\n * (`CmsValidationIssue`, EC-469) — so a new source joins by mapping to this, not by\n * pretending to be a customization issue.\n *\n * `severity` was added by EC-493: both mappers used to drop it and the widget painted\n * every finding amber, which stopped being a style nit once EC-488 re-derived\n * `shadowedMountSpaces` from `error` to `warning` ON THE REASONING that the two mean\n * different things, and EC-482 shipped one producer (`hierarchyMountConflicts`)\n * emitting both into this very list.\n */\nexport interface NavIssueItem {\n key: string\n message: string\n severity: IssueSeverity\n}\n\n/**\n * Customization issues, as rail-foot items — localized (EC-478). The producers\n * (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a\n * catalog key + identifier values rather than a sentence, because the EC-236 seam\n * lives in `packages/core` and may not import the studio catalog;\n * {@link customizationIssueMessage} resolves them here, so this list renders wholly in\n * the active locale instead of half of it.\n *\n * EC-493: `severity` rides along. It used to be dropped here, which is half of why an\n * error and a warning were indistinguishable at the foot — `CustomizationIssue` has\n * carried the field since EC-236, the mapper simply did not read it.\n */\nexport function customizationItems(issues: readonly CustomizationIssue[]): NavIssueItem[] {\n return issues.map((issue, index) => ({\n key: `${issue.point}:${issue.id ?? index}`,\n message: customizationIssueMessage(issue),\n severity: issue.severity,\n }))\n}\n\n/**\n * URL-map validation issues (EC-469), as rail-foot items.\n *\n * Copy follows the issues-pane convention (EC-192): the localized plain-language label,\n * then the detail — both localized since EC-487, which gave `CmsValidationIssue` the\n * same catalog-reference carriage EC-478 gave `CustomizationIssue`.\n *\n * EC-493: `severity` rides along, the other half of the fix. It matters most here:\n * EC-482's `hierarchyMountConflicts` emits `error` for two mounts on the same path and\n * `warning` for a nested pair FROM ONE FUNCTION, and EC-488 re-derived\n * `shadowedMountSpaces` to `warning` precisely because the fall-through made it\n * conditional. Both land in this list.\n */\nexport function routeIssueItems(issues: readonly CmsValidationIssue[]): NavIssueItem[] {\n return issues.map((issue, index) => ({\n key: `route-issue:${issue.code}:${issue.path.join('/') || index}`,\n message: `${cmsIssueLabel(issue.code, issue.severity)} — ${cmsValidationIssueMessage(issue)}`,\n severity: issue.severity,\n }))\n}\n\n/** The stable key of the one orphaned-collections finding (EC-606). */\nexport const ORPHANED_COLLECTIONS_ISSUE_KEY = 'orphaned-collections'\n\n/**\n * EC-606 — stored documents whose collection the project does not declare, as ONE\n * rail-foot item (or none).\n *\n * Session hydration keeps only the declared collections' documents and counts the\n * rest (`HydratedCms.orphans`, via core's `orphanedCollections`). Before EC-606 those\n * rows were dropped with no trace, so renaming a collection id in config made its\n * documents vanish from the studio. One item for the whole list: it is one condition\n * with one remedy, and N cards would crowd out the rail.\n *\n * `warning`, not `error`: nothing is lost (the rows are stored, untouched) and nothing\n * the editor does is blocked. The message names only one remedy, declaring the\n * collection again or restoring its old id (B6). `documents.clear` plus a re-seed\n * would also remove the warning, but for a renamed collection on a live site that is\n * data loss, so the message does not suggest it. A published count rides each entry\n * that has one: those rows are still on the public delivery snapshot.\n */\nexport function orphanedCollectionItems(orphans: readonly OrphanedCollection[]): NavIssueItem[] {\n if (orphans.length === 0) return []\n const collections = orphans\n .map(({ collection, count, published }) =>\n published > 0\n ? translate('shell.nav.orphanedCollections.itemPublished', { collection, count, published })\n : translate('shell.nav.orphanedCollections.item', { collection, count }),\n )\n .join(', ')\n return [\n {\n key: ORPHANED_COLLECTIONS_ISSUE_KEY,\n message: translate('shell.nav.orphanedCollections', { collections, file: PROJECT_CONFIG_FILE }),\n severity: 'warning',\n },\n ]\n}\n\n/**\n * Visible project-level issues at the rail foot — the seam's \"never silent\" (EC-236,\n * extended by EC-467's reuse issues, EC-469's URL-map issues, EC-479's config\n * issues and EC-606's orphaned collections).\n *\n * EC-493: an `error` and a `warning` render distinguishably here, in the treatment the\n * document Issues pane already uses. Each finding is its own tinted card (as in the\n * pane) rather than one shared amber box, because a shared box cannot express a mixed\n * list; collapsed, the single glyph follows {@link highestIssueSeverity}. EC-507 moved\n * that treatment to `components/issue-severity.ts`, the one home all three surfaces\n * import from.\n *\n * `data-nav-issue-severity` is on each card (and on the collapsed glyph) as the\n * machine-readable form of the same distinction — what the smoke test pins, and what an\n * agent inspecting the DOM reads without decoding Tailwind classes.\n */\nexport function NavIssues({ issues, collapsed }: { issues: NavIssueItem[]; collapsed: boolean }) {\n if (collapsed) {\n const severity = highestIssueSeverity(issues)\n const Icon = issueSeverityIcon(severity)\n return (\n <div\n className={cn('grid h-9 place-items-center', issueSeverityStyle(severity).text)}\n data-nav-issue-severity={severity}\n title={issues.map((issue) => issue.message).join('\\n')}\n >\n <Icon\n className=\"size-4\"\n aria-label={translate('shell.nav.issuesCount', { count: issues.length })}\n />\n </div>\n )\n }\n return (\n <Stack gap=\"1\" className=\"mb-1\">\n {issues.map((issue) => (\n <NavIssueCard key={issue.key} issue={issue} />\n ))}\n </Stack>\n )\n}\n\n/** One finding at the rail foot, tinted + glyphed by its severity. */\nfunction NavIssueCard({ issue }: { issue: NavIssueItem }) {\n const style = issueSeverityStyle(issue.severity)\n const Icon = issueSeverityIcon(issue.severity)\n return (\n <Row\n gap=\"1\"\n align=\"start\"\n // `gap-1.5` (the pre-EC-493 icon/text spacing) merged over the primitive's `gap-1`.\n className={cn('gap-1.5 rounded-md border px-2 py-1.5', style.card, style.text)}\n data-nav-issue-severity={issue.severity}\n title={issue.message}\n >\n <Icon className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden />\n <Text size=\"xs\" className=\"min-w-0 leading-snug\">\n {issue.message}\n </Text>\n </Row>\n )\n}\n"]}
1
+ {"version":3,"file":"nav-issues.js","sourceRoot":"","sources":["../../src/components/nav-issues.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAiBjE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqC;IACtE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACnC,GAAG,EAAE,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,IAAI,KAAK,EAAE;QAC1C,OAAO,EAAE,yBAAyB,CAAC,KAAK,CAAC;QACzC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE;YACR,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,KAAK,CAAC,KAAK,KAAK,oBAAoB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YACvE,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD;KACF,CAAC,CAAC,CAAA;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,MAAqC;IACnE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACjC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;QAC1C,OAAO;YACL,GAAG,EAAE,eAAe,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,EAAE;YACjE,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC;YAChD,OAAO,EAAE,yBAAyB,CAAC,KAAK,CAAC;YACzC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,8BAA8B,GAAG,sBAAsB,CAAA;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAsC;IAC5E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,MAAM,WAAW,GAAG,OAAO;SACxB,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,CACxC,SAAS,GAAG,CAAC;QACX,CAAC,CAAC,SAAS,CAAC,6CAA6C,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC5F,CAAC,CAAC,SAAS,CAAC,oCAAoC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAC3E;SACA,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,OAAO;QACL;YACE,GAAG,EAAE,8BAA8B;YACnC,OAAO,EAAE,SAAS,CAAC,+BAA+B,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC;YAC/F,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE;SACpD;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { CmsValidationIssue, OrphanedCollection } from '@elytracms/core/cms-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { translate } from '../lib/i18n'\nimport { PROJECT_CONFIG_FILE, PROJECT_CONFIG_POINT } from '../lib/workspace'\nimport {\n cmsIssueLabel,\n cmsValidationIssueMessage,\n customizationIssueMessage,\n} from '../lib/persona/labels'\nimport type { IssueSeverity } from './issue-severity'\nimport type { NavIssue } from './nav-issues-registry'\nimport { routeIssueLocation } from './notifications/notice-model'\n\n/**\n * The engine's own project-level findings, mapped to the one shape the issues tray lists\n * (EC-690; before it, the rail foot's cards). The least common shape across the sources —\n * customization issues (`CustomizationIssue`) and URL-map validation (`CmsValidationIssue`,\n * EC-469) — so a new source joins by mapping to this, not by pretending to be a\n * customization issue.\n *\n * `severity` was added by EC-493: both mappers used to drop it and the widget painted\n * every finding amber. EC-690 added `title` (the short label a row leads with) and\n * `location` (where \"Go to\" leads, or the file the row names).\n */\nexport type NavIssueItem = Required<Pick<NavIssue, 'key' | 'message'>> & {\n severity: IssueSeverity\n} & Pick<NavIssue, 'title' | 'location'>\n\n/**\n * Customization issues, as findings — localized (EC-478). The producers\n * (`studio-core/customization.ts`, `default-modules.tsx`, `builder/reuse.ts`) emit a\n * catalog key + identifier values rather than a sentence, because the EC-236 seam\n * lives in `packages/core` and may not import the studio catalog;\n * {@link customizationIssueMessage} resolves them here.\n *\n * EC-690: located where the developer fixes them — the project config's own issues\n * (EC-479, point `project`) in `elytra.config.ts`, every other point (the rail, field\n * renderers, app views, reuse, the AI endpoint) in the studio config (`defineStudio`).\n */\nexport function customizationItems(issues: readonly CustomizationIssue[]): NavIssueItem[] {\n return issues.map((issue, index) => ({\n key: `${issue.point}:${issue.id ?? index}`,\n message: customizationIssueMessage(issue),\n severity: issue.severity,\n location: {\n kind: 'config',\n declaredIn: issue.point === PROJECT_CONFIG_POINT ? 'project' : 'studio',\n ...(issue.id !== undefined ? { entry: issue.id } : {}),\n },\n }))\n}\n\n/**\n * URL-map validation issues (EC-469), as findings.\n *\n * EC-690: the localized plain-language label (EC-192) is the row's `title`, the detail its\n * `message` — the two used to be joined into one sentence. A redirect finding is located at\n * its row in the redirects rail (by record kind), a route finding at `elytra.config.ts`.\n *\n * EC-493: `severity` rides along — EC-482's `hierarchyMountConflicts` emits `error` and\n * `warning` from one function, and both land here.\n */\nexport function routeIssueItems(issues: readonly CmsValidationIssue[]): NavIssueItem[] {\n return issues.map((issue, index) => {\n const location = routeIssueLocation(issue)\n return {\n key: `route-issue:${issue.code}:${issue.path.join('/') || index}`,\n title: cmsIssueLabel(issue.code, issue.severity),\n message: cmsValidationIssueMessage(issue),\n severity: issue.severity,\n ...(location !== undefined ? { location } : {}),\n }\n })\n}\n\n/** The stable key of the one orphaned-collections finding (EC-606). */\nexport const ORPHANED_COLLECTIONS_ISSUE_KEY = 'orphaned-collections'\n\n/**\n * EC-606 — stored documents whose collection the project does not declare, as ONE\n * finding (or none).\n *\n * Session hydration keeps only the declared collections' documents and counts the\n * rest (`HydratedCms.orphans`, via core's `orphanedCollections`). Before EC-606 those\n * rows were dropped with no trace, so renaming a collection id in config made its\n * documents vanish from the studio. One item for the whole list: it is one condition\n * with one remedy, and N rows would crowd out the rest.\n *\n * `warning`, not `error`: nothing is lost (the rows are stored, untouched) and nothing\n * the editor does is blocked. The message names only one remedy, declaring the\n * collection again or restoring its old id (B6). `documents.clear` plus a re-seed\n * would also remove the warning, but for a renamed collection on a live site that is\n * data loss, so the message does not suggest it. A published count rides each entry\n * that has one: those rows are still on the public delivery snapshot.\n */\nexport function orphanedCollectionItems(orphans: readonly OrphanedCollection[]): NavIssueItem[] {\n if (orphans.length === 0) return []\n const collections = orphans\n .map(({ collection, count, published }) =>\n published > 0\n ? translate('shell.nav.orphanedCollections.itemPublished', { collection, count, published })\n : translate('shell.nav.orphanedCollections.item', { collection, count }),\n )\n .join(', ')\n return [\n {\n key: ORPHANED_COLLECTIONS_ISSUE_KEY,\n message: translate('shell.nav.orphanedCollections', { collections, file: PROJECT_CONFIG_FILE }),\n severity: 'warning',\n location: { kind: 'config', declaredIn: 'project' },\n },\n ]\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Project } from '@elytracms/core/studio-core';
3
+ import type { ToastItem } from '../../lib/notices/toast-store.js';
4
+ import type { IssueSeverity } from '../issue-severity.js';
5
+ import type { Finding, GoToTarget } from './notice-model.js';
6
+ /**
7
+ * EC-690 — the studio's ONE notification-and-issue surface, in the shell header: a badge
8
+ * counting what needs attention by severity (EC-507's treatment), opening a tray that lists
9
+ * every live notice and every persistent finding with where it is ("Go to" a rail row or a
10
+ * document; the file or setting for code). Owner ruling (a), 2026-10-28: ONE badge, in the
11
+ * header — the rail foot shows none (and no finding text at all any more).
12
+ *
13
+ * The tray is a Radix popover bounded to the content area (`boundary` = `<main>`), so it can
14
+ * never cover the section rail at any width. Notices also PEEK in the content area's
15
+ * bottom-right ({@link NoticePeek}) when they are raised.
16
+ */
17
+ export declare function NoticeCenter({ project, boundary }: {
18
+ project: Project;
19
+ boundary: Element | null;
20
+ }): import("react").JSX.Element;
21
+ /** What the badge counts: every finding, plus every live error notice (an error stays until dismissed). */
22
+ export declare function noticeCounts(findings: readonly Finding[], notices: readonly ToastItem[]): Record<IssueSeverity, number>;
23
+ /** "2 errors, 1 warning" — or "No issues". */
24
+ export declare function noticeSummary(counts: Record<IssueSeverity, number>): string;
25
+ /** The badge's face: one count per severity present, or a muted "all clear". */
26
+ export declare function NoticeBadgeContent({ counts }: {
27
+ counts: Record<IssueSeverity, number>;
28
+ }): import("react").JSX.Element;
29
+ /**
30
+ * The tray's body: live notices first (newest first), then the findings in the one order.
31
+ * Pure over its inputs — `target` resolves a finding's location, so a test renders it without
32
+ * a router (a finding with no link target renders no `Link`).
33
+ */
34
+ export declare function NoticeTray({ projectId, findings, notices, target, onNavigate, }: {
35
+ projectId: string;
36
+ findings: readonly Finding[];
37
+ notices: readonly ToastItem[];
38
+ target: (finding: Finding) => GoToTarget | null;
39
+ onNavigate?: () => void;
40
+ }): import("react").JSX.Element;
41
+ /**
42
+ * One persistent finding: EC-507's card + glyph for its severity, a bold title, the message
43
+ * (clamped, full text on hover; long unbreakable paths wrap — `overflow-wrap:anywhere`), and
44
+ * its location action. No dismiss and no collapse (owner ruling (e)): it leaves when it stops
45
+ * being true.
46
+ */
47
+ export declare function FindingRow({ finding, action }: {
48
+ finding: Finding;
49
+ action?: ReactNode;
50
+ }): import("react").JSX.Element;
51
+ /** One transient notice: EC-507's palette for an error, the success tone otherwise; always dismissable. */
52
+ export declare function NoticeRow({ notice }: {
53
+ notice: ToastItem;
54
+ }): import("react").JSX.Element;
55
+ /**
56
+ * New notices, peeking for 5 s in the content area's bottom-right — inside `<main>`, so they
57
+ * never cover the section rail (the old `Toaster` was fixed to the viewport and did, at
58
+ * narrow widths), and not at its top-right, where the editor's Publish button and save status
59
+ * sit (critic point 8). An error leaves the peek but stays in the tray until dismissed.
60
+ */
61
+ export declare function NoticePeek(): import("react").JSX.Element | null;