@micha.bigler/ui-core-micha 2.18.0 → 2.20.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/WORK_ORDERS.md +4 -2
- package/dev/entries.jsx +17 -2
- package/dist/i18n/messagingTranslations.js +1 -0
- package/dist/messaging/ConversationList.js +15 -3
- package/dist/messaging/DirectMessageLauncher.js +5 -2
- package/dist/messaging/MessagingProvider.js +37 -2
- package/dist/messaging/Thread.js +21 -2
- package/docs/messaging-deviations.md +10 -10
- package/package.json +1 -1
- package/src/i18n/messagingTranslations.ts +1 -0
- package/src/messaging/ConversationList.jsx +13 -3
- package/src/messaging/DirectMessageLauncher.jsx +7 -2
- package/src/messaging/MessagingProvider.jsx +35 -2
- package/src/messaging/Thread.jsx +19 -2
- package/tests/messagingDirectMessageLauncher.test.jsx +41 -0
- package/tests/messagingThreadReceipts.test.jsx +170 -0
- package/tests/messagingUnreadLifecycle.test.jsx +4 -1
- package/work-orders/DX-1.md +4 -1
- package/work-orders/MSG-3d.md +268 -0
- package/work-orders/MSG-3e.md +164 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# MSG-3e — per-candidate scope in `DirectMessageLauncher`
|
|
2
|
+
|
|
3
|
+
Status: planned · Tier 2 · Target repo: `ui-core-micha` (main)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Part A — Envelope (2026-08-02)
|
|
8
|
+
|
|
9
|
+
### Goal
|
|
10
|
+
|
|
11
|
+
Let a host present **one flat direct-message picker whose candidates live in different messaging
|
|
12
|
+
scopes**. Today `DirectMessageLauncher` takes a single `scope` prop and applies it to every candidate,
|
|
13
|
+
so a host with people spread across several scopes must either render one launcher per scope or
|
|
14
|
+
re-implement the component.
|
|
15
|
+
|
|
16
|
+
### Why now
|
|
17
|
+
|
|
18
|
+
jg-ferien MSG-6 turns `/messages` into a global inbox across **all** of a user's events. In jg's model a
|
|
19
|
+
direct conversation is scoped to an event, so a flat "everyone I may DM" list carries a **different
|
|
20
|
+
scope per person** — the operator's explicit requirement ("DM über alle, Gruppen pro Event"). Without
|
|
21
|
+
this seam jg would have to copy ~90 lines of this component locally, which AGENTS.md's
|
|
22
|
+
reuse-before-reimplement rule exists to prevent. The need is generic: any host whose people span
|
|
23
|
+
multiple scopes hits it.
|
|
24
|
+
|
|
25
|
+
### Expected outcome
|
|
26
|
+
|
|
27
|
+
`candidates[n].scope` (optional) takes precedence over the `scope` prop **for the selected candidate
|
|
28
|
+
only**. Precisely:
|
|
29
|
+
|
|
30
|
+
| selected candidate's `scope` | `scope` prop | scope sent to `createDirectConversation` |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| present | present | the candidate's |
|
|
33
|
+
| present | absent | the candidate's |
|
|
34
|
+
| absent | present | the prop's (today's behaviour) |
|
|
35
|
+
| absent | absent | no `scope` key in the payload at all (today's behaviour) |
|
|
36
|
+
|
|
37
|
+
Strictly additive and backward compatible: a host that passes no per-candidate scope sees byte-identical
|
|
38
|
+
behaviour. Note the last row — the current code omits the key entirely rather than sending
|
|
39
|
+
`scope: null`, and that must not change; dcm distinguishes the two.
|
|
40
|
+
|
|
41
|
+
### Scope / non-goals
|
|
42
|
+
|
|
43
|
+
In scope: `src/messaging/DirectMessageLauncher.jsx` and its test file.
|
|
44
|
+
|
|
45
|
+
Explicit non-goals — do **not**:
|
|
46
|
+
- change the dialog's layout, copy, i18n keys, or a11y semantics;
|
|
47
|
+
- add grouping, sectioning, or per-candidate secondary text (a scope is not a label — jg groups its
|
|
48
|
+
*group* picker by event locally, and that stays a host concern);
|
|
49
|
+
- touch `MessagingProvider`'s `openDirectConversation` signature or any other component;
|
|
50
|
+
- validate or interpret the scope value — it is an opaque host-supplied identifier, passed through.
|
|
51
|
+
|
|
52
|
+
### Tier
|
|
53
|
+
|
|
54
|
+
Tier 2. Shared-core (`ui-core-micha`) is excluded from the Tier-1 fast path by AGENTS.md regardless of
|
|
55
|
+
diff size, so this one-line-of-logic change gets a WO, Codex-first, and a mandatory independent review.
|
|
56
|
+
|
|
57
|
+
### Tests to WRITE (scoped — run only these)
|
|
58
|
+
|
|
59
|
+
Extend `tests/messagingDirectMessageLauncher.test.jsx`:
|
|
60
|
+
|
|
61
|
+
1. **per-candidate scope wins** — candidates `[{id: 2, display_name: 'Alex', scope: 'scope-a'}, {id: 3,
|
|
62
|
+
display_name: 'Sam', scope: 'scope-b'}]`, launcher `scope="scope-prop"`; select Sam, start; assert
|
|
63
|
+
`createDirectConversation` called with `{target_user_id: 3, scope: 'scope-b'}`.
|
|
64
|
+
2. **fallback to the prop** (regression) — a candidate without its own `scope` in the same mixed list
|
|
65
|
+
still sends the prop's scope.
|
|
66
|
+
3. **neither present** — no `scope` key in the payload (`expect(Object.keys(payload)).toEqual(
|
|
67
|
+
['target_user_id'])` or equivalent — asserting absence, not `undefined`).
|
|
68
|
+
|
|
69
|
+
The three existing tests in that file must keep passing unchanged; they are the backward-compatibility
|
|
70
|
+
proof and must not be edited to accommodate the change.
|
|
71
|
+
|
|
72
|
+
Command: `pnpm vitest run tests/messagingDirectMessageLauncher.test.jsx`
|
|
73
|
+
|
|
74
|
+
### Release
|
|
75
|
+
|
|
76
|
+
One version bump `2.19.0` → **`2.20.0`** (minor: additive, backward-compatible) + npm publish at WO end.
|
|
77
|
+
jg-ferien MSG-6 pins this version, so the publish is the precondition for that WO's final test run.
|
|
78
|
+
Per `reference_dcm_ucm_publish_topology`: publish-from-main, and the target version must be verified
|
|
79
|
+
live on the registry before jg bumps its pin.
|
|
80
|
+
|
|
81
|
+
### Risks
|
|
82
|
+
|
|
83
|
+
Low. The one real trap is the "absent/absent" row above — a naive `scope: candidate.scope ?? scope`
|
|
84
|
+
inside the object literal would start sending `scope: undefined`, which serialises differently and
|
|
85
|
+
changes an existing contract. Build the payload conditionally, as the current code already does.
|
|
86
|
+
|
|
87
|
+
### Execution directive
|
|
88
|
+
|
|
89
|
+
**If you are the implementer reading this as your own spec, this section is not addressed to you — it
|
|
90
|
+
tells the Orchestrator how to invoke you. You ARE that invocation: do not shell out to `codex exec`.**
|
|
91
|
+
|
|
92
|
+
Orchestrator: implement through `codex exec` in the background, invoked **directly via Bash** (never the
|
|
93
|
+
`debugger`/`*_coder` Agent wrappers), with **both** flags `--skip-git-repo-check` and
|
|
94
|
+
`--dangerously-bypass-approvals-and-sandbox`, the WO passed as a positional argument from this committed
|
|
95
|
+
file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit; if the
|
|
96
|
+
fallback is used the Orchestrator becomes author and an independent `reviewer` is mandatory.
|
|
97
|
+
|
|
98
|
+
### Mini-handover (pastable)
|
|
99
|
+
|
|
100
|
+
Orchestrator: implement `work-orders/MSG-3e.md` in `ui-core-micha` (main). `git pull` first, read the WO,
|
|
101
|
+
then follow `orchestrate-codex` (Codex-first, own independent review, publish 2.20.0 at WO end).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Part B — Implementation map (Orchestrator)
|
|
106
|
+
|
|
107
|
+
**Single chunk.** **Target repo working directory:** `C:\Users\Micha Bigler\Documents\webapps\ui-core-micha`
|
|
108
|
+
(repo root). Work from this package; open only the named files to verify.
|
|
109
|
+
|
|
110
|
+
### The one file that changes
|
|
111
|
+
|
|
112
|
+
`src/messaging/DirectMessageLauncher.jsx` — 96 lines, whole file is in scope but only `start()` and one
|
|
113
|
+
derivation change.
|
|
114
|
+
|
|
115
|
+
Current state (verified while scoping, do not re-derive):
|
|
116
|
+
|
|
117
|
+
- `:25` — `export function DirectMessageLauncher({ candidates = [], scope, onOpen })`.
|
|
118
|
+
- `:31` — the component tracks **only** `selectedId`, never the selected candidate object. A lookup is
|
|
119
|
+
therefore needed; mirror the pattern jg already uses in its own `AllGroupsLauncher`
|
|
120
|
+
(`jg-ferien/frontend/src/pages/messaging/AllGroupsLauncher.jsx:40-44`):
|
|
121
|
+
`const selectedCandidate = useMemo(() => candidates.find((c) => c.id === selectedId) || null,
|
|
122
|
+
[candidates, selectedId]);` — `useMemo` is not yet imported in this file (`:1` imports `useId, useState`).
|
|
123
|
+
- `:44-60` — `start()`; the payload is built at `:49-52`:
|
|
124
|
+
```js
|
|
125
|
+
const conversation = await openDirectConversation({
|
|
126
|
+
target_user_id: selectedId,
|
|
127
|
+
...(scope == null ? {} : { scope }),
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
This spread-or-nothing shape is the invariant the third test pins: when there is no scope the key is
|
|
131
|
+
**absent**, not `undefined`. Keep the shape; only the value fed into it changes:
|
|
132
|
+
`const effectiveScope = selectedCandidate?.scope ?? scope;` then `...(effectiveScope == null ? {} :
|
|
133
|
+
{ scope: effectiveScope })`. `??` (not `||`) so a falsy-but-valid scope id survives.
|
|
134
|
+
- `:75-82` — the `ListItemButton` render loop; **do not** change it. Candidate rendering already falls
|
|
135
|
+
back through `display_name || name || label || id` and the scope must not appear in the UI.
|
|
136
|
+
|
|
137
|
+
### Invariants / do-not-touch
|
|
138
|
+
|
|
139
|
+
- `extractApiErrorMessage` error path (`:55-59`) and the `starting` guard stay exactly as they are.
|
|
140
|
+
- `openDirectConversation` comes from `useMessaging()` (`:19, :27`) — its signature is dcm's contract,
|
|
141
|
+
unchanged.
|
|
142
|
+
- `src/index.js:72` already exports the component; no export change.
|
|
143
|
+
- No new i18n keys (`MessagingDirect.*` set is complete for this change).
|
|
144
|
+
|
|
145
|
+
### Docs
|
|
146
|
+
|
|
147
|
+
`docs/messaging-deviations.md` currently has zero BLOCKED entries (MSG-3d closed them) — this WO adds no
|
|
148
|
+
deviation and must not add a row. If the repo carries a prop-level API note for this component, update it;
|
|
149
|
+
if not, do not invent a docs file.
|
|
150
|
+
|
|
151
|
+
### Progress contract
|
|
152
|
+
|
|
153
|
+
Emit a `PLAN: …` line first, then a single-line `PROGRESS: [n/total] <action>` before every relevant
|
|
154
|
+
action and `… done` on completion, no gap > ~2 min, unbuffered stdout, and exactly one final
|
|
155
|
+
`RESULT: DONE|BLOCKED <reason>`.
|
|
156
|
+
|
|
157
|
+
### Constraints for the implementer
|
|
158
|
+
|
|
159
|
+
The WO text is the COMPLETE spec. Read `AGENTS.md` and `.codex/skills/frontend-engineering` only for
|
|
160
|
+
conventions. Stay in scope. Do not touch the Core-Behaviour approval surfaces. Do not update `MEMORY.md`.
|
|
161
|
+
Do **not** `git add/commit/push` — leave the diff for the Orchestrator's review. Do **not** bump the
|
|
162
|
+
version or publish — the Orchestrator owns the release. WRITE the three tests above and RUN ONLY that one
|
|
163
|
+
test file; no full suite, no self-review — the Orchestrator owns the authoritative run and the
|
|
164
|
+
independent review, and those are the gate.
|