anton-bakker-feedback 0.4.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +171 -0
  2. package/dist/chunks/{i18n-Cba0xHU-.js → i18n-Cn2yFMbd.js} +4 -2
  3. package/dist/chunks/i18n-Cn2yFMbd.js.map +1 -0
  4. package/dist/chunks/ingest-transport-DAA_pJNb.js +245 -0
  5. package/dist/chunks/ingest-transport-DAA_pJNb.js.map +1 -0
  6. package/dist/chunks/normalisers-Cbd-i4Jh.js +97 -0
  7. package/dist/chunks/normalisers-Cbd-i4Jh.js.map +1 -0
  8. package/dist/chunks/{observability-CfwHKObh.js → observability-DUK725zv.js} +3 -3
  9. package/dist/chunks/{observability-CfwHKObh.js.map → observability-DUK725zv.js.map} +1 -1
  10. package/dist/chunks/{submitResolver-DsfkrUHY.js → submitResolver-wfr4kbEM.js} +60 -39
  11. package/dist/chunks/submitResolver-wfr4kbEM.js.map +1 -0
  12. package/dist/core/config.d.ts +11 -0
  13. package/dist/core/config.d.ts.map +1 -1
  14. package/dist/core/index.d.ts +1 -0
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/ingest-transport.d.ts +97 -0
  17. package/dist/core/ingest-transport.d.ts.map +1 -1
  18. package/dist/core/normalisers.d.ts +29 -0
  19. package/dist/core/normalisers.d.ts.map +1 -0
  20. package/dist/core/observability.d.ts.map +1 -1
  21. package/dist/core/request-headers.d.ts +22 -0
  22. package/dist/core/request-headers.d.ts.map +1 -0
  23. package/dist/core/state-metrics.d.ts +19 -0
  24. package/dist/core/state-metrics.d.ts.map +1 -0
  25. package/dist/core/transforms.d.ts.map +1 -1
  26. package/dist/core/types.d.ts +93 -3
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/index.js +17 -99
  29. package/dist/index.js.map +1 -1
  30. package/dist/react/FeedbackConfirm.d.ts.map +1 -1
  31. package/dist/react/FeedbackDialogue.d.ts.map +1 -1
  32. package/dist/react/admin/ActivityTimeline.d.ts +9 -0
  33. package/dist/react/admin/ActivityTimeline.d.ts.map +1 -0
  34. package/dist/react/admin/AnnotationComposer.d.ts +30 -0
  35. package/dist/react/admin/AnnotationComposer.d.ts.map +1 -0
  36. package/dist/react/admin/FeedbackListPage.d.ts.map +1 -1
  37. package/dist/react/admin/FeedbackUpdatePage.d.ts +6 -0
  38. package/dist/react/admin/FeedbackUpdatePage.d.ts.map +1 -1
  39. package/dist/react/admin/LifecycleControls.d.ts +28 -0
  40. package/dist/react/admin/LifecycleControls.d.ts.map +1 -0
  41. package/dist/react/admin/__tests__/ActivityTimeline.test.d.ts +2 -0
  42. package/dist/react/admin/__tests__/ActivityTimeline.test.d.ts.map +1 -0
  43. package/dist/react/admin/__tests__/AnnotationComposer.test.d.ts +2 -0
  44. package/dist/react/admin/__tests__/AnnotationComposer.test.d.ts.map +1 -0
  45. package/dist/react/admin/__tests__/LifecycleControls.test.d.ts +2 -0
  46. package/dist/react/admin/__tests__/LifecycleControls.test.d.ts.map +1 -0
  47. package/dist/react/admin/hooks/can.d.ts +47 -0
  48. package/dist/react/admin/hooks/can.d.ts.map +1 -0
  49. package/dist/react/admin/hooks/useStatusTransition.d.ts +16 -2
  50. package/dist/react/admin/hooks/useStatusTransition.d.ts.map +1 -1
  51. package/dist/react/admin/index.d.ts +14 -1
  52. package/dist/react/admin/index.d.ts.map +1 -1
  53. package/dist/react/admin/index.js +312 -29
  54. package/dist/react/admin/index.js.map +1 -1
  55. package/dist/react/admin/types.d.ts +13 -0
  56. package/dist/react/admin/types.d.ts.map +1 -1
  57. package/dist/react/resolver/submitResolver.d.ts +10 -7
  58. package/dist/react/resolver/submitResolver.d.ts.map +1 -1
  59. package/dist/react.js +26 -9
  60. package/dist/react.js.map +1 -1
  61. package/dist/server/index.d.ts +4 -5
  62. package/dist/server/index.d.ts.map +1 -1
  63. package/dist/server/listHandler.d.ts +15 -8
  64. package/dist/server/listHandler.d.ts.map +1 -1
  65. package/dist/server.js +22 -170
  66. package/dist/server.js.map +1 -1
  67. package/package.json +13 -1
  68. package/dist/chunks/i18n-Cba0xHU-.js.map +0 -1
  69. package/dist/chunks/submitResolver-DsfkrUHY.js.map +0 -1
  70. package/dist/server/FeedbackService.d.ts +0 -46
  71. package/dist/server/FeedbackService.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"FeedbackListPage.d.ts","sourceRoot":"","sources":["../../../src/react/admin/FeedbackListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAsB,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAiBxD,iBAAS,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,qBAE1D;AAED,iBAAS,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,qBAElD;AAED,QAAA,MAAM,gBAAgB,8FA0FrB,CAAC;AAGF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"FeedbackListPage.d.ts","sourceRoot":"","sources":["../../../src/react/admin/FeedbackListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAsB,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAwBxD,iBAAS,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,qBAE1D;AAED,iBAAS,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,qBAElD;AAED,QAAA,MAAM,gBAAgB,8FA0FrB,CAAC;AAGF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC"}
@@ -3,6 +3,12 @@ import { FeedbackUpdatePageProps } from './types.js';
3
3
  * FeedbackUpdatePage.tsx
4
4
  * Location: src/react/admin/FeedbackUpdatePage.tsx
5
5
  *
6
+ * @deprecated This component is deprecated and will be removed in a future version.
7
+ * Use FeedbackAnnotateClient.appendActivity() and FeedbackAnnotateClient.transitionStatus()
8
+ * from ingest-transport.ts instead. The whole-array corrections[] PATCH has been removed
9
+ * to prevent data loss (sagrosoft20-class silent drop). This shell is kept for backward
10
+ * compatibility during stage-3 consumer migration.
11
+ *
6
12
  * Admin update page for editing mutable fields of a feedback item.
7
13
  * Uses UpdatePage + EnumField + TextField + TextareaField from anton-bakker-tailwind-components.
8
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"FeedbackUpdatePage.d.ts","sourceRoot":"","sources":["../../../src/react/admin/FeedbackUpdatePage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAiC1D,QAAA,MAAM,kBAAkB,gGAiMvB,CAAC;AAGF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"FeedbackUpdatePage.d.ts","sourceRoot":"","sources":["../../../src/react/admin/FeedbackUpdatePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAiC1D,QAAA,MAAM,kBAAkB,gGA6LvB,CAAC;AAGF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { StoredFeedbackItem } from '../../core/types.js';
2
+ import { FeedbackAnnotateClient } from '../../core/ingest-transport.js';
3
+ import { CapabilityKey } from './hooks/can.js';
4
+ /**
5
+ * LifecycleControls.tsx
6
+ * Location: src/react/admin/LifecycleControls.tsx
7
+ *
8
+ * Legal+authorized transition buttons, assignment, duplicate merge via transitionStatus.
9
+ * Surfaces AnnotateResult.conflictReason inline.
10
+ * Renders only transition buttons for targets where can(requiredCapability) is true.
11
+ */
12
+ import * as React from 'react';
13
+ export interface LifecycleControlsProps {
14
+ feedbackId: string;
15
+ item: StoredFeedbackItem;
16
+ currentUser: {
17
+ userId: string;
18
+ name?: string;
19
+ };
20
+ can: (cap: CapabilityKey) => boolean;
21
+ transitionStatus: FeedbackAnnotateClient['transitionStatus'];
22
+ onTransitioned?: () => void;
23
+ }
24
+ /**
25
+ * LifecycleControls renders only transition buttons for targets where can(requiredCapability) is true.
26
+ */
27
+ export declare const LifecycleControls: React.ForwardRefExoticComponent<LifecycleControlsProps & React.RefAttributes<HTMLDivElement>>;
28
+ //# sourceMappingURL=LifecycleControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LifecycleControls.d.ts","sourceRoot":"","sources":["../../../src/react/admin/LifecycleControls.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAA2B,MAAM,gCAAgC,CAAC;AACtG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,kBAAkB,CAAC;IACzB,WAAW,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,GAAG,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACrC,gBAAgB,EAAE,sBAAsB,CAAC,kBAAkB,CAAC,CAAC;IAC7D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AA8BD;;GAEG;AACH,eAAO,MAAM,iBAAiB,+FA6H7B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ActivityTimeline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActivityTimeline.test.d.ts","sourceRoot":"","sources":["../../../../src/react/admin/__tests__/ActivityTimeline.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AnnotationComposer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnnotationComposer.test.d.ts","sourceRoot":"","sources":["../../../../src/react/admin/__tests__/AnnotationComposer.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=LifecycleControls.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LifecycleControls.test.d.ts","sourceRoot":"","sources":["../../../../src/react/admin/__tests__/LifecycleControls.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ import { StoredFeedbackItem } from '../../../core/types.js';
2
+
3
+ /**
4
+ * Capability keys for feedback operations.
5
+ * Each key grants permission for a specific action on feedback items.
6
+ */
7
+ export type CapabilityKey = 'feedback:comment' | 'feedback:annotate' | 'feedback:triage' | 'feedback:assign' | 'feedback:resolve' | 'feedback:verify' | 'feedback:reopen' | 'feedback:reject' | 'feedback:delete';
8
+ /**
9
+ * Record mapping each capability key to itself.
10
+ * Facilitates autocomplete and exhaustiveness checking.
11
+ */
12
+ export declare const CAPABILITIES: Record<CapabilityKey, CapabilityKey>;
13
+ /**
14
+ * User identity with resolved capabilities.
15
+ * Consuming apps (PEP) translate their identity system to this format at the system boundary.
16
+ */
17
+ export interface CapabilityUser {
18
+ userId: string;
19
+ capabilities: Set<CapabilityKey>;
20
+ }
21
+ /** Alias for CapabilityUser — both names refer to the same type. */
22
+ export type UserWithCapabilities = CapabilityUser;
23
+ /**
24
+ * Pure capability-based authorization guard.
25
+ * Returns true if the user has the specified capability OR if ownership rules grant it.
26
+ *
27
+ * Ownership rules:
28
+ * - Submitters (userId === item.submittedBy) can comment and annotate without explicit capability
29
+ * - Reporters (userId in item.reporters[].userId) can comment and annotate without explicit capability
30
+ * - All other capabilities require explicit presence in user.capabilities
31
+ *
32
+ * @param capability - The capability being checked
33
+ * @param item - The feedback item
34
+ * @param user - The user with resolved capabilities
35
+ * @returns true if authorized, false otherwise
36
+ *
37
+ * @example
38
+ * const user = { userId: 'alice', capabilities: new Set(['feedback:triage']) };
39
+ * can('feedback:triage', item, user) // → true (explicit capability)
40
+ * can('feedback:comment', item, user) // → false (no capability, not owner)
41
+ *
42
+ * const submitter = { userId: item.submittedBy, capabilities: new Set([]) };
43
+ * can('feedback:comment', item, submitter) // → true (ownership rule)
44
+ * can('feedback:triage', item, submitter) // → false (ownership doesn't grant admin capabilities)
45
+ */
46
+ export declare function can(capability: CapabilityKey, item: StoredFeedbackItem, user: CapabilityUser): boolean;
47
+ //# sourceMappingURL=can.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"can.d.ts","sourceRoot":"","sources":["../../../../src/react/admin/hooks/can.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB,kBAAkB,GAClB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,CAU7D,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;CAClC;AAED,oEAAoE;AACpE,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,GAAG,CACjB,UAAU,EAAE,aAAa,EACzB,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,cAAc,GACnB,OAAO,CAiBT"}
@@ -1,5 +1,19 @@
1
1
  import { FeedbackStatus } from '../../../core/types.js';
2
2
 
3
+ /**
4
+ * Exhaustive legal transitions for the 12-state QA lifecycle. Readonly so consumers and
5
+ * tests can enumerate legal pairs. No self-loops.
6
+ */
7
+ export declare const QA_LIFECYCLE_TRANSITIONS: Readonly<Record<FeedbackStatus, readonly FeedbackStatus[]>>;
8
+ /**
9
+ * Pure predicate: is a transition from `from` to `to` legal in the QA lifecycle?
10
+ * Self-transitions are never legal.
11
+ *
12
+ * @param from - current status
13
+ * @param to - proposed target status
14
+ * @returns true if the transition is encoded in QA_LIFECYCLE_TRANSITIONS
15
+ */
16
+ export declare function canTransitionTo(from: FeedbackStatus, to: FeedbackStatus): boolean;
3
17
  export interface UseStatusTransitionResult {
4
18
  /** Statuses reachable from the current status (current status excluded) */
5
19
  allowedTargets: FeedbackStatus[];
@@ -11,8 +25,8 @@ export interface UseStatusTransitionResult {
11
25
  * @returns Object with `allowedTargets` — statuses the item can transition to
12
26
  *
13
27
  * @example
14
- * const { allowedTargets } = useStatusTransition('open');
15
- * // allowedTargets → ['approved', 'deferred', 'wontfix']
28
+ * const { allowedTargets } = useStatusTransition('new');
29
+ * // allowedTargets → ['triaged']
16
30
  */
17
31
  export declare function useStatusTransition(currentStatus: FeedbackStatus): UseStatusTransitionResult;
18
32
  //# sourceMappingURL=useStatusTransition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStatusTransition.d.ts","sourceRoot":"","sources":["../../../../src/react/admin/hooks/useStatusTransition.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAc7D,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,cAAc,EAAE,cAAc,EAAE,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,cAAc,GAAG,yBAAyB,CAG5F"}
1
+ {"version":3,"file":"useStatusTransition.d.ts","sourceRoot":"","sources":["../../../../src/react/admin/hooks/useStatusTransition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC,CAahG,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,cAAc,GAAG,OAAO,CAGjF;AAED,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,cAAc,EAAE,cAAc,EAAE,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,cAAc,GAAG,yBAAyB,CAG5F"}
@@ -1,7 +1,20 @@
1
1
  export { FeedbackListPage, StatusBadge, TypeBadge } from './FeedbackListPage.js';
2
2
  export { FeedbackDetailPage } from './FeedbackDetailPage.js';
3
+ /**
4
+ * @deprecated FeedbackUpdatePage is superseded by the enriched FeedbackDetailPage with appendActivity + transitionStatus.
5
+ * Use createIngestAnnotateClient + FeedbackDetailPage with the new IoC props instead.
6
+ * This export is kept for backward compatibility and will be removed in a future major version.
7
+ */
3
8
  export { FeedbackUpdatePage } from './FeedbackUpdatePage.js';
9
+ export { ActivityTimeline } from './ActivityTimeline.js';
10
+ export { AnnotationComposer } from './AnnotationComposer.js';
11
+ export { LifecycleControls } from './LifecycleControls.js';
4
12
  export { useStatusTransition } from './hooks/useStatusTransition.js';
13
+ export { can, CAPABILITIES } from './hooks/can.js';
14
+ export type { CapabilityKey, CapabilityUser, UserWithCapabilities } from './hooks/can.js';
5
15
  export type { AdminPageProps, FeedbackListPageProps, FeedbackDetailPageProps, FeedbackUpdatePageProps, } from './types.js';
6
- export type { StoredFeedbackItem, FeedbackStatus, FeedbackType, FeedbackScope, FeedbackSize, FeedbackEnvironment, Reporter, DialogueEntry, CorrectionEntry, FeedbackContext, } from '../../core/types.js';
16
+ export type { StoredFeedbackItem, FeedbackStatus, FeedbackType, FeedbackScope, FeedbackSize, FeedbackEnvironment, Reporter, DialogueEntry, CorrectionEntry, FeedbackContext, ActivityEntry, ActivityKind, ActivityActor, } from '../../core/types.js';
17
+ export type { FeedbackAnnotateClient, AnnotateResult, TransitionStatusOptions, } from '../../core/ingest-transport.js';
18
+ export { createIngestAnnotateClient } from '../../core/ingest-transport.js';
19
+ export { normaliseStoredItem } from '../../core/normalisers.js';
7
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/admin/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAGrE,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,aAAa,EACb,eAAe,EACf,eAAe,GAChB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/admin/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAGrE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG1F,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,YAAY,EACV,sBAAsB,EACtB,cAAc,EACd,uBAAuB,GACxB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -1,12 +1,21 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { ListPage, Badge, DetailPage, Button, DetailSection, DetailRow, UpdatePage, EnumField, TextField, TextareaField, SaveFooter } from "anton-bakker-tailwind-components";
4
+ import { d } from "../../chunks/ingest-transport-DAA_pJNb.js";
5
+ import { c } from "../../chunks/normalisers-Cbd-i4Jh.js";
4
6
  const STATUS_VARIANT = {
5
- open: "warning",
6
- approved: "default",
7
+ new: "warning",
8
+ triaged: "default",
9
+ assigned: "default",
10
+ in_progress: "default",
11
+ fixed: "success",
12
+ pending_retest: "warning",
13
+ verified: "success",
14
+ closed: "success",
7
15
  deferred: "secondary",
8
- resolved: "success",
9
- wontfix: "destructive"
16
+ rejected: "destructive",
17
+ duplicate: "secondary",
18
+ reopened: "warning"
10
19
  };
11
20
  const TYPE_VARIANT = {
12
21
  bug: "destructive",
@@ -21,7 +30,7 @@ function TypeBadge({ type }) {
21
30
  return /* @__PURE__ */ jsx(Badge, { variant: TYPE_VARIANT[type] ?? "secondary", children: type });
22
31
  }
23
32
  const FeedbackListPage = React.forwardRef(
24
- ({ items, loading = false, onNavigate, className, emptyStateMessage, currentUser, ...rest }, ref) => {
33
+ ({ items, loading = false, onNavigate, className, emptyStateMessage, currentUser: _currentUser }, ref) => {
25
34
  const [search, setSearch] = React.useState("");
26
35
  const [statusFilter, setStatusFilter] = React.useState("");
27
36
  const [typeFilter, setTypeFilter] = React.useState("");
@@ -135,7 +144,7 @@ function parseStructuredContent(content) {
135
144
  return { header, fields, raw: content };
136
145
  }
137
146
  const FeedbackDetailPage = React.forwardRef(
138
- ({ feedbackId, fetchItem, getScreenshotUrl, onNavigate, currentUser, className }, ref) => {
147
+ ({ feedbackId, fetchItem, getScreenshotUrl, onNavigate, currentUser: _currentUser, className }, ref) => {
139
148
  const [item, setItem] = React.useState(null);
140
149
  const [loading, setLoading] = React.useState(true);
141
150
  const [error, setError] = React.useState(null);
@@ -224,15 +233,22 @@ const FeedbackDetailPage = React.forwardRef(
224
233
  }
225
234
  );
226
235
  FeedbackDetailPage.displayName = "FeedbackDetailPage";
227
- const ALLOWED_TRANSITIONS = {
228
- open: ["approved", "deferred", "wontfix"],
229
- approved: ["open", "resolved", "deferred"],
230
- deferred: ["open", "approved", "wontfix"],
231
- resolved: ["open", "approved"],
232
- wontfix: ["open", "deferred"]
236
+ const QA_LIFECYCLE_TRANSITIONS = {
237
+ new: ["triaged"],
238
+ triaged: ["assigned", "deferred", "rejected", "duplicate"],
239
+ assigned: ["in_progress", "deferred"],
240
+ in_progress: ["fixed", "deferred"],
241
+ fixed: ["pending_retest"],
242
+ pending_retest: ["verified", "reopened"],
243
+ verified: ["closed", "reopened"],
244
+ closed: ["reopened"],
245
+ rejected: ["reopened"],
246
+ duplicate: ["reopened"],
247
+ reopened: ["triaged", "assigned", "in_progress"],
248
+ deferred: ["triaged"]
233
249
  };
234
250
  function useStatusTransition(currentStatus) {
235
- const allowedTargets = ALLOWED_TRANSITIONS[currentStatus] ?? [];
251
+ const allowedTargets = [...QA_LIFECYCLE_TRANSITIONS[currentStatus] ?? []];
236
252
  return { allowedTargets };
237
253
  }
238
254
  const STATUS_OPTIONS = [
@@ -266,7 +282,7 @@ const FeedbackUpdatePage = React.forwardRef(
266
282
  const [item, setItem] = React.useState(null);
267
283
  const [loading, setLoading] = React.useState(true);
268
284
  const [saving, setSaving] = React.useState(false);
269
- const [status, setStatus] = React.useState("open");
285
+ const [status, setStatus] = React.useState("new");
270
286
  const [type, setType] = React.useState("bug");
271
287
  const [scope, setScope] = React.useState("");
272
288
  const [size, setSize] = React.useState("");
@@ -300,6 +316,10 @@ const FeedbackUpdatePage = React.forwardRef(
300
316
  const isDirty = item && (status !== item.status || type !== item.type || scope !== (item.scope ?? "") || size !== (item.size ?? "") || assignedTo !== (item.assignedTo ?? "") || tags !== (item.tags?.join(", ") ?? ""));
301
317
  const handleSave = async () => {
302
318
  if (!item) return;
319
+ if (!updateItem) {
320
+ console.warn("FeedbackUpdatePage: updateItem prop not provided. This component is deprecated.");
321
+ return;
322
+ }
303
323
  setSaving(true);
304
324
  try {
305
325
  const patch = { lastModifiedAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -310,23 +330,10 @@ const FeedbackUpdatePage = React.forwardRef(
310
330
  if (assignedTo !== (item.assignedTo ?? "")) patch.assignedTo = assignedTo || void 0;
311
331
  const newTags = tags.split(",").map((t) => t.trim()).filter(Boolean);
312
332
  if (JSON.stringify(newTags) !== JSON.stringify(item.tags ?? [])) patch.tags = newTags;
313
- if (status === "resolved" && item.status !== "resolved") {
333
+ if (status === "closed" && item.status !== "closed") {
314
334
  patch.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
315
335
  patch.resolvedBy = currentUser?.userId ?? "unknown";
316
336
  }
317
- if (classificationChanged && correctionNote) {
318
- patch.corrections = [
319
- ...item.corrections ?? [],
320
- {
321
- field: [type !== item.type && "type", scope !== (item.scope ?? "") && "scope", size !== (item.size ?? "") && "size"].filter(Boolean).join(", "),
322
- before: { type: item.type, scope: item.scope, size: item.size },
323
- after: { type, scope: scope || void 0, size: size || void 0 },
324
- correctedBy: currentUser?.userId ?? "unknown",
325
- correctedAt: (/* @__PURE__ */ new Date()).toISOString(),
326
- reason: correctionNote
327
- }
328
- ];
329
- }
330
337
  await updateItem(feedbackId, patch);
331
338
  onNavigate(`/feedback/${feedbackId}`);
332
339
  } finally {
@@ -346,7 +353,7 @@ const FeedbackUpdatePage = React.forwardRef(
346
353
  Button,
347
354
  {
348
355
  onClick: handleSave,
349
- disabled: saving || !isDirty || !!classificationChanged && !correctionNote,
356
+ disabled: saving || !isDirty,
350
357
  children: saving ? "Saving…" : "Save changes"
351
358
  }
352
359
  )
@@ -441,12 +448,288 @@ const FeedbackUpdatePage = React.forwardRef(
441
448
  }
442
449
  );
443
450
  FeedbackUpdatePage.displayName = "FeedbackUpdatePage";
451
+ function ActivityTimeline({ activity, className }) {
452
+ if (activity.length === 0) {
453
+ return /* @__PURE__ */ jsx("div", { "data-testid": "activity-timeline", className, children: /* @__PURE__ */ jsx("div", { "data-testid": "activity-empty-state", children: "No activity yet." }) });
454
+ }
455
+ return /* @__PURE__ */ jsx("div", { "data-testid": "activity-timeline", className, children: activity.map((entry) => /* @__PURE__ */ jsxs("div", { "data-testid": "activity-entry", children: [
456
+ /* @__PURE__ */ jsx("span", { "data-testid": "activity-kind-badge", children: entry.kind.replace(/_/g, " ") }),
457
+ /* @__PURE__ */ jsx("span", { "data-testid": "activity-actor", children: entry.actor.displayName }),
458
+ /* @__PURE__ */ jsx("span", { "data-testid": "activity-timestamp", children: entry.occurredAt }),
459
+ entry.body !== void 0 && /* @__PURE__ */ jsx("span", { "data-testid": "activity-body", children: entry.body })
460
+ ] }, entry.id)) });
461
+ }
462
+ const AnnotationComposer = React.forwardRef(
463
+ (props, ref) => {
464
+ const { feedbackId, currentUser, can: can2, appendActivity, appSource, onAppended } = props;
465
+ const canComment = can2("feedback:comment");
466
+ const canAnnotate = can2("feedback:annotate");
467
+ const [body, setBody] = React.useState("");
468
+ const [conflictReason, setConflictReason] = React.useState(void 0);
469
+ const [submitting, setSubmitting] = React.useState(false);
470
+ if (!canComment && !canAnnotate) return null;
471
+ const submit = async (kind) => {
472
+ setSubmitting(true);
473
+ setConflictReason(void 0);
474
+ const entry = {
475
+ id: crypto.randomUUID(),
476
+ actor: { userId: currentUser.userId, displayName: currentUser.name ?? currentUser.userId },
477
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
478
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
479
+ source: appSource,
480
+ kind,
481
+ body,
482
+ schemaVersion: 1
483
+ };
484
+ const result = await appendActivity(feedbackId, entry);
485
+ if (result.success) {
486
+ setBody("");
487
+ setConflictReason(void 0);
488
+ onAppended?.();
489
+ } else {
490
+ setConflictReason(result.conflictReason ?? result.error ?? "Unknown error");
491
+ }
492
+ setSubmitting(false);
493
+ };
494
+ const isDisabled = body.trim().length === 0 || submitting;
495
+ return /* @__PURE__ */ jsxs("div", { ref, "data-testid": "annotation-composer", className: "space-y-3", children: [
496
+ /* @__PURE__ */ jsx(
497
+ "textarea",
498
+ {
499
+ "data-testid": "annotation-textarea",
500
+ value: body,
501
+ onChange: (e) => setBody(e.target.value),
502
+ placeholder: "Add a comment, note, question, or answer...",
503
+ className: "w-full min-h-[100px] p-3 border border-input rounded-md bg-background text-sm resize-y focus:outline-none focus:ring-2 focus:ring-ring"
504
+ }
505
+ ),
506
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
507
+ canComment && /* @__PURE__ */ jsx(
508
+ Button,
509
+ {
510
+ "data-testid": "annotation-comment-button",
511
+ disabled: isDisabled,
512
+ onClick: () => {
513
+ void submit("comment");
514
+ },
515
+ variant: "default",
516
+ size: "sm",
517
+ children: "Comment"
518
+ }
519
+ ),
520
+ canAnnotate && /* @__PURE__ */ jsxs(Fragment, { children: [
521
+ /* @__PURE__ */ jsx(
522
+ Button,
523
+ {
524
+ "data-testid": "annotation-note-button",
525
+ disabled: isDisabled,
526
+ onClick: () => {
527
+ void submit("note");
528
+ },
529
+ variant: "outline",
530
+ size: "sm",
531
+ children: "Note"
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsx(
535
+ Button,
536
+ {
537
+ "data-testid": "annotation-question-button",
538
+ disabled: isDisabled,
539
+ onClick: () => {
540
+ void submit("question");
541
+ },
542
+ variant: "outline",
543
+ size: "sm",
544
+ children: "Question"
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsx(
548
+ Button,
549
+ {
550
+ "data-testid": "annotation-answer-button",
551
+ disabled: isDisabled,
552
+ onClick: () => {
553
+ void submit("answer");
554
+ },
555
+ variant: "outline",
556
+ size: "sm",
557
+ children: "Answer"
558
+ }
559
+ )
560
+ ] })
561
+ ] }),
562
+ conflictReason !== void 0 && /* @__PURE__ */ jsx("div", { "data-testid": "annotation-conflict-reason", className: "text-sm text-destructive", children: conflictReason })
563
+ ] });
564
+ }
565
+ );
566
+ AnnotationComposer.displayName = "AnnotationComposer";
567
+ const CAPABILITY_MAP = {
568
+ triaged: "feedback:triage",
569
+ assigned: "feedback:assign",
570
+ in_progress: "feedback:assign",
571
+ fixed: "feedback:resolve",
572
+ pending_retest: "feedback:resolve",
573
+ verified: "feedback:verify",
574
+ closed: "feedback:resolve",
575
+ deferred: "feedback:triage",
576
+ rejected: "feedback:reject",
577
+ duplicate: "feedback:reject",
578
+ reopened: "feedback:reopen"
579
+ };
580
+ function formatStatusLabel(status) {
581
+ return status.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
582
+ }
583
+ const LifecycleControls = React.forwardRef(
584
+ (props, ref) => {
585
+ const { feedbackId, item, can: can2, transitionStatus, onTransitioned } = props;
586
+ const { allowedTargets } = useStatusTransition(item.status);
587
+ const authorizedTargets = allowedTargets.filter((target) => {
588
+ const cap = CAPABILITY_MAP[target];
589
+ return cap !== void 0 && can2(cap);
590
+ });
591
+ const [duplicateInputVisible, setDuplicateInputVisible] = React.useState(false);
592
+ const [duplicateOf, setDuplicateOf] = React.useState("");
593
+ const [conflictReason, setConflictReason] = React.useState(void 0);
594
+ const [submitting, setSubmitting] = React.useState(false);
595
+ if (authorizedTargets.length === 0) return null;
596
+ const handleTransition = async (target) => {
597
+ if (target === "duplicate") {
598
+ if (!duplicateInputVisible) {
599
+ setDuplicateInputVisible(true);
600
+ return;
601
+ }
602
+ if (duplicateOf.trim().length === 0) {
603
+ setConflictReason("Duplicate target ID is required");
604
+ return;
605
+ }
606
+ setSubmitting(true);
607
+ setConflictReason(void 0);
608
+ const opts2 = {
609
+ currentStatus: item.status,
610
+ duplicateOf,
611
+ duplicateReporters: item.reporters
612
+ };
613
+ const result2 = await transitionStatus(feedbackId, "duplicate", opts2);
614
+ if (result2.success) {
615
+ setDuplicateInputVisible(false);
616
+ setDuplicateOf("");
617
+ setConflictReason(void 0);
618
+ onTransitioned?.();
619
+ } else {
620
+ setConflictReason(result2.conflictReason ?? result2.error ?? "Unknown error");
621
+ }
622
+ setSubmitting(false);
623
+ return;
624
+ }
625
+ setSubmitting(true);
626
+ setConflictReason(void 0);
627
+ const opts = {
628
+ currentStatus: item.status
629
+ };
630
+ const result = await transitionStatus(feedbackId, target, opts);
631
+ if (result.success) {
632
+ setConflictReason(void 0);
633
+ onTransitioned?.();
634
+ } else {
635
+ setConflictReason(result.conflictReason ?? result.error ?? "Unknown error");
636
+ }
637
+ setSubmitting(false);
638
+ };
639
+ return /* @__PURE__ */ jsxs("div", { ref, "data-testid": "lifecycle-controls", className: "space-y-3", children: [
640
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children: authorizedTargets.map((target) => /* @__PURE__ */ jsx(
641
+ Button,
642
+ {
643
+ "data-testid": `lifecycle-${target}-button`,
644
+ onClick: () => {
645
+ void handleTransition(target);
646
+ },
647
+ disabled: submitting,
648
+ variant: target === "rejected" || target === "duplicate" ? "destructive" : "default",
649
+ size: "sm",
650
+ children: formatStatusLabel(target)
651
+ },
652
+ target
653
+ )) }),
654
+ duplicateInputVisible && /* @__PURE__ */ jsxs("div", { "data-testid": "duplicate-input-container", className: "space-y-2", children: [
655
+ /* @__PURE__ */ jsx(
656
+ "input",
657
+ {
658
+ "data-testid": "duplicate-of-input",
659
+ value: duplicateOf,
660
+ onChange: (e) => setDuplicateOf(e.target.value),
661
+ placeholder: "Canonical feedback ID (e.g., FB-2026-11-01-abc12345)",
662
+ className: "w-full p-2 border border-input rounded-md bg-background text-sm focus:outline-none focus:ring-2 focus:ring-ring"
663
+ }
664
+ ),
665
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
666
+ /* @__PURE__ */ jsx(
667
+ Button,
668
+ {
669
+ onClick: () => {
670
+ void handleTransition("duplicate");
671
+ },
672
+ disabled: submitting || duplicateOf.trim().length === 0,
673
+ size: "sm",
674
+ children: "Confirm Duplicate"
675
+ }
676
+ ),
677
+ /* @__PURE__ */ jsx(
678
+ Button,
679
+ {
680
+ onClick: () => {
681
+ setDuplicateInputVisible(false);
682
+ setDuplicateOf("");
683
+ setConflictReason(void 0);
684
+ },
685
+ disabled: submitting,
686
+ variant: "outline",
687
+ size: "sm",
688
+ children: "Cancel"
689
+ }
690
+ )
691
+ ] })
692
+ ] }),
693
+ conflictReason !== void 0 && /* @__PURE__ */ jsx("div", { "data-testid": "lifecycle-conflict-reason", className: "text-sm text-destructive", children: conflictReason })
694
+ ] });
695
+ }
696
+ );
697
+ LifecycleControls.displayName = "LifecycleControls";
698
+ const CAPABILITIES = {
699
+ "feedback:comment": "feedback:comment",
700
+ "feedback:annotate": "feedback:annotate",
701
+ "feedback:triage": "feedback:triage",
702
+ "feedback:assign": "feedback:assign",
703
+ "feedback:resolve": "feedback:resolve",
704
+ "feedback:verify": "feedback:verify",
705
+ "feedback:reopen": "feedback:reopen",
706
+ "feedback:reject": "feedback:reject",
707
+ "feedback:delete": "feedback:delete"
708
+ };
709
+ function can(capability, item, user) {
710
+ if (user.capabilities.has(capability)) {
711
+ return true;
712
+ }
713
+ const isSubmitter = user.userId === item.submittedBy;
714
+ const isReporter = item.reporters.some((r) => r.userId === user.userId);
715
+ if ((isSubmitter || isReporter) && (capability === "feedback:comment" || capability === "feedback:annotate")) {
716
+ return true;
717
+ }
718
+ return false;
719
+ }
444
720
  export {
721
+ ActivityTimeline,
722
+ AnnotationComposer,
723
+ CAPABILITIES,
445
724
  FeedbackDetailPage,
446
725
  FeedbackListPage,
447
726
  FeedbackUpdatePage,
727
+ LifecycleControls,
448
728
  StatusBadge,
449
729
  TypeBadge,
730
+ can,
731
+ d as createIngestAnnotateClient,
732
+ c as normaliseStoredItem,
450
733
  useStatusTransition
451
734
  };
452
735
  //# sourceMappingURL=index.js.map