wenay-react2 1.0.47 → 1.0.49
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/README.md +14 -14
- package/doc/EXAMPLE_USAGE.md +969 -969
- package/doc/PROJECT_FUNCTIONALITY.md +403 -403
- package/doc/PROJECT_RULES.md +27 -27
- package/doc/WENAY_REACT2_RENAMES.md +170 -170
- package/doc/changes/v1.0.35.md +18 -18
- package/doc/changes/v1.0.38.md +37 -37
- package/doc/changes/v1.0.39.md +23 -23
- package/doc/changes/v1.0.40.md +14 -14
- package/doc/changes/v1.0.41.md +35 -35
- package/doc/changes/v1.0.42.md +26 -26
- package/doc/changes/v1.0.43.md +10 -10
- package/doc/changes/v1.0.44.md +14 -14
- package/doc/changes/v1.0.45.md +8 -8
- package/doc/changes/v1.0.46.md +11 -11
- package/doc/changes/v1.0.47.md +9 -9
- package/doc/changes/v1.0.48.md +8 -0
- package/doc/changes/v1.0.49.md +7 -0
- package/doc/examples/README.md +5 -4
- package/doc/examples/conference-client.html +34 -0
- package/doc/examples/conference-client.ts +212 -0
- package/doc/examples/conference-server.mjs +150 -0
- package/doc/examples/peer-call-media.tsx +7 -7
- package/doc/examples/stand.tsx +5 -5
- package/doc/native.md +37 -37
- package/doc/progress/README.md +13 -13
- package/doc/progress/architecture-fix-queue.md +74 -74
- package/doc/progress/column-state-present-gate.md +28 -28
- package/doc/progress/common2-adoption-1.0.73.md +28 -28
- package/doc/progress/common2-adoption-1.0.74.md +24 -24
- package/doc/progress/hook-controller-opportunities.md +363 -363
- package/doc/progress/hook-extraction-audit.md +195 -195
- package/doc/progress/public-surface-normalization.md +351 -351
- package/doc/progress/qa-stand-walkthrough-2026-07-12.md +62 -0
- package/doc/progress/stand-as-examples-audit.md +20 -20
- package/doc/progress/style-system-normalization.md +121 -121
- package/doc/target/README.md +32 -32
- package/doc/target/my.md +134 -124
- package/doc/wenay-react2-rare.md +807 -807
- package/doc/wenay-react2.md +543 -541
- package/lib/common/demo/fakeRtcLoopback.d.ts +17 -0
- package/lib/common/demo/fakeRtcLoopback.js +108 -0
- package/lib/common/demo/peerConference.d.ts +449 -0
- package/lib/common/demo/peerConference.js +372 -0
- package/lib/common/demo/peerMedia.js +6 -1
- package/lib/common/src/hooks/index.d.ts +1 -0
- package/lib/common/src/hooks/index.js +1 -0
- package/lib/common/src/hooks/useRoute.d.ts +28 -0
- package/lib/common/src/hooks/useRoute.js +30 -0
- package/lib/common/src/logs/logsController.js +2 -2
- package/lib/common/testUseReact/qa.js +4 -3
- package/lib/style/menuRight.css +19 -19
- package/lib/style/style.css +23 -23
- package/lib/style/tokens.css +184 -184
- package/package.json +2 -1
- package/doc/wenay-react2-1.0.8.tgz +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Conference bridge server: the REAL-backend variant of QA card 46 / demo/peer-conference.
|
|
2
|
+
// One Node process owns the peer host (call signaling + WebRTC negotiation pass-through),
|
|
3
|
+
// the media relay and the ROOM policy; browser seats connect over Socket.IO from
|
|
4
|
+
// conference-client.html. Media rides the server relay (technology 1); the peer-store
|
|
5
|
+
// links between seats can promote to a real RTCPeerConnection datachannel negotiated
|
|
6
|
+
// through this same signal hub (technology 2) and re-interpose back.
|
|
7
|
+
//
|
|
8
|
+
// Run: node doc/examples/conference-server.mjs (needs `socket.io` installed)
|
|
9
|
+
// Then open the client page from the wenay-react2 stand dev server in 2-3 tabs:
|
|
10
|
+
// /doc/examples/conference-client.html?me=a&peers=b,c&host=1
|
|
11
|
+
// /doc/examples/conference-client.html?me=b&peers=a,c
|
|
12
|
+
// /doc/examples/conference-client.html?me=c&peers=a,b
|
|
13
|
+
import {createServer} from "node:http";
|
|
14
|
+
import {Server as SocketIOServer} from "socket.io";
|
|
15
|
+
import {createRpcServerAuto, listen, Peer} from "wenay-common2";
|
|
16
|
+
|
|
17
|
+
const PORT = Number(process.env.PORT ?? 8391);
|
|
18
|
+
|
|
19
|
+
// ============== room policy: an accepted call joins BOTH parties to its room ==============
|
|
20
|
+
// Membership is reference-counted per (room, account): in a host-star each member holds
|
|
21
|
+
// one call to the host, while the host holds one call per member. canWatch/canSignal ask
|
|
22
|
+
// "do these two accounts share a live room" — the ONLY media/endpoint authority here.
|
|
23
|
+
function createRoomPolicy() {
|
|
24
|
+
const calls = new Map(); // pair -> {caller, callee, room, state, timer}
|
|
25
|
+
const rooms = new Map(); // room -> Map<account, refCount>
|
|
26
|
+
|
|
27
|
+
function join(room, account) {
|
|
28
|
+
const members = rooms.get(room) ?? new Map();
|
|
29
|
+
members.set(account, (members.get(account) ?? 0) + 1);
|
|
30
|
+
rooms.set(room, members);
|
|
31
|
+
}
|
|
32
|
+
function leave(room, account) {
|
|
33
|
+
const members = rooms.get(room);
|
|
34
|
+
if (!members) return;
|
|
35
|
+
const count = (members.get(account) ?? 0) - 1;
|
|
36
|
+
if (count > 0) members.set(account, count); else members.delete(account);
|
|
37
|
+
if (!members.size) rooms.delete(room);
|
|
38
|
+
}
|
|
39
|
+
function sameRoom(a, b) {
|
|
40
|
+
for (const members of rooms.values()) if (members.has(a) && members.has(b)) return true;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
function finish(pair, reason) {
|
|
44
|
+
const call = calls.get(pair);
|
|
45
|
+
if (!call) return;
|
|
46
|
+
calls.delete(pair);
|
|
47
|
+
clearTimeout(call.timer);
|
|
48
|
+
if (call.state === "active") {
|
|
49
|
+
leave(call.room, call.caller);
|
|
50
|
+
leave(call.room, call.callee);
|
|
51
|
+
}
|
|
52
|
+
console.log(`[conf] call down: ${call.caller} <-> ${call.callee} in "${call.room}" (${reason})`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function authorize(env) {
|
|
56
|
+
if (env.type === "ring") {
|
|
57
|
+
const room = typeof env.session?.room === "string" ? env.session.room : null;
|
|
58
|
+
if (!env.pair.startsWith("call:") || env.from === env.to || !room || calls.has(env.pair)) return false;
|
|
59
|
+
const timer = setTimeout(() => finish(env.pair, "expired"), 35_000);
|
|
60
|
+
timer.unref?.();
|
|
61
|
+
calls.set(env.pair, {caller: env.from, callee: env.to, room, state: "ringing", timer});
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (env.type === "accept" || env.type === "decline" || env.type === "hangup") {
|
|
65
|
+
const call = calls.get(env.pair);
|
|
66
|
+
if (!call) return false;
|
|
67
|
+
const reverse = env.from === call.callee && env.to === call.caller;
|
|
68
|
+
const participant = reverse || (env.from === call.caller && env.to === call.callee);
|
|
69
|
+
if (env.type === "accept") {
|
|
70
|
+
if (call.state !== "ringing" || !reverse) return false;
|
|
71
|
+
call.state = "active";
|
|
72
|
+
clearTimeout(call.timer);
|
|
73
|
+
join(call.room, call.caller);
|
|
74
|
+
join(call.room, call.callee);
|
|
75
|
+
console.log(`[conf] call up: ${call.caller} <-> ${call.callee} joined "${call.room}"`);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (env.type === "decline" && (!reverse || call.state !== "ringing")) return false;
|
|
79
|
+
if (!participant) return false;
|
|
80
|
+
finish(env.pair, env.type);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
// WebRTC direct negotiation (offer/answer/ice) is endpoint exposure: the server
|
|
84
|
+
// only brokers it between accounts that currently share a room.
|
|
85
|
+
if (env.type === "offer" || env.type === "answer" || env.type === "ice") {
|
|
86
|
+
return sameRoom(env.from, env.to);
|
|
87
|
+
}
|
|
88
|
+
return true; // close/revoke pass through (session teardown)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function dropAccount(account) {
|
|
92
|
+
for (const [pair, call] of calls) {
|
|
93
|
+
if (call.caller === account || call.callee === account) finish(pair, "offline");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
authorize,
|
|
99
|
+
canWatch: (watcher, owner) => watcher !== owner && sameRoom(watcher, owner),
|
|
100
|
+
dropAccount,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const policy = createRoomPolicy();
|
|
105
|
+
const media = Peer.createMediaRelay({lines: {cam: "video"}, videoHistory: 8, canWatch: policy.canWatch});
|
|
106
|
+
const host = Peer.createPeerHost({authorize: policy.authorize});
|
|
107
|
+
|
|
108
|
+
host.presence.changes.on(change => {
|
|
109
|
+
console.log(`[conf] presence: ${change.account} ${change.online ? "online" : "offline"}`);
|
|
110
|
+
if (change.online) return;
|
|
111
|
+
// media BEFORE policy: relay.dropAccount closes watcher views through their
|
|
112
|
+
// policy proxies, so the grants must still be alive while it runs (common2
|
|
113
|
+
// 1.0.74 throws on already-revoked views — see peer-media-relay dropAccount)
|
|
114
|
+
try { media.dropAccount(change.account); } catch (error) { console.log(`[conf] media drop ${change.account}: ${error}`); }
|
|
115
|
+
policy.dropAccount(change.account);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const httpServer = createServer((_req, res) => { res.statusCode = 404; res.end("conference signaling server"); });
|
|
119
|
+
const ioServer = new SocketIOServer(httpServer, {cors: {origin: true}, maxHttpBufferSize: 1e7});
|
|
120
|
+
|
|
121
|
+
ioServer.on("connection", socket => {
|
|
122
|
+
const account = String(socket.handshake.auth?.account ?? "anon");
|
|
123
|
+
const peer = host.connection(account);
|
|
124
|
+
const [disconnect, disconnectListen] = listen();
|
|
125
|
+
socket.on("disconnect", () => { disconnect(); peer.close(); });
|
|
126
|
+
createRpcServerAuto({
|
|
127
|
+
socket: {emit: (key, data) => socket.emit(key, data), on: (key, cb) => socket.on(key, cb)},
|
|
128
|
+
socketKey: "app",
|
|
129
|
+
object: {
|
|
130
|
+
serverTime: () => new Date().toISOString(),
|
|
131
|
+
// the fragment is exposed verbatim: the browser's CallManager, peer-store
|
|
132
|
+
// client and WebRTC connector all ride this one signal port over the socket
|
|
133
|
+
peer: peer.fragment,
|
|
134
|
+
media: {
|
|
135
|
+
publish: media.publishOf(account),
|
|
136
|
+
watch: media.watchOf(account),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
disconnectListen,
|
|
140
|
+
});
|
|
141
|
+
console.log(`[conf] ${account} connected`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
httpServer.listen(PORT, () => {
|
|
145
|
+
console.log(`[conf] conference bridge is up on :${PORT}`);
|
|
146
|
+
console.log("[conf] open the client page from the stand dev server, e.g.");
|
|
147
|
+
console.log(" /doc/examples/conference-client.html?me=a&peers=b,c&host=1");
|
|
148
|
+
console.log(" /doc/examples/conference-client.html?me=b&peers=a,c");
|
|
149
|
+
console.log(" /doc/examples/conference-client.html?me=c&peers=a,b");
|
|
150
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is the same interactive component used by the QA stand cards 41–44.
|
|
3
|
-
* It is shipped as a supported demo entrypoint, not a private test helper.
|
|
4
|
-
*
|
|
5
|
-
* import {PeerCallDemo, PeerPresenceDemo, MediaRelayAclDemo, MediaRelayAudioDemo}
|
|
6
|
-
* from "wenay-react2/demo/peer-media"
|
|
7
|
-
*/
|
|
1
|
+
/**
|
|
2
|
+
* This is the same interactive component used by the QA stand cards 41–44.
|
|
3
|
+
* It is shipped as a supported demo entrypoint, not a private test helper.
|
|
4
|
+
*
|
|
5
|
+
* import {PeerCallDemo, PeerPresenceDemo, MediaRelayAclDemo, MediaRelayAudioDemo}
|
|
6
|
+
* from "wenay-react2/demo/peer-media"
|
|
7
|
+
*/
|
|
8
8
|
export {PeerCallDemo, PeerPresenceDemo, MediaRelayAclDemo, MediaRelayAudioDemo} from "wenay-react2/demo/peer-media";
|
package/doc/examples/stand.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** The complete interactive wenay-react2 stand, shipped in the package. */
|
|
2
|
-
export {QABoard} from "wenay-react2/demo/stand";
|
|
3
|
-
|
|
4
|
-
// import {createRoot} from "react-dom/client";
|
|
5
|
-
// import {QABoard} from "wenay-react2/demo/stand";
|
|
1
|
+
/** The complete interactive wenay-react2 stand, shipped in the package. */
|
|
2
|
+
export {QABoard} from "wenay-react2/demo/stand";
|
|
3
|
+
|
|
4
|
+
// import {createRoot} from "react-dom/client";
|
|
5
|
+
// import {QABoard} from "wenay-react2/demo/stand";
|
|
6
6
|
// createRoot(document.getElementById("root")!).render(<QABoard />);
|
package/doc/native.md
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# React Native entrypoint
|
|
2
|
-
|
|
3
|
-
Import the renderer-neutral API from `wenay-react2/native`. It has no React DOM,
|
|
4
|
-
CSS, browser-global or ag-grid dependency.
|
|
5
|
-
|
|
6
|
-
```ts
|
|
7
|
-
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
8
|
-
import {createNativeColumnDots, createNativeColumnState} from 'wenay-react2/native'
|
|
9
|
-
|
|
10
|
-
const columns = createNativeColumnState({
|
|
11
|
-
key: 'super-admin.columns',
|
|
12
|
-
columns: [
|
|
13
|
-
{key: 'server', title: 'Server', fixed: true},
|
|
14
|
-
{key: 'status', title: 'Status'},
|
|
15
|
-
{key: 'panic', title: 'Panic'},
|
|
16
|
-
],
|
|
17
|
-
storage: AsyncStorage,
|
|
18
|
-
})
|
|
19
|
-
await columns.ready
|
|
20
|
-
|
|
21
|
-
const dots = createNativeColumnDots({state: columns, max: 8})
|
|
22
|
-
dots.begin('status', 40, 0)
|
|
23
|
-
dots.move(120, 0, {start: 0, length: 240})
|
|
24
|
-
dots.end()
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The persisted `v/order/visible/width/sort/filter/groups` shape matches the web
|
|
28
|
-
`ColumnsConfig`. Writes are serialized and local edits made while hydration is
|
|
29
|
-
pending win over stale storage.
|
|
30
|
-
|
|
31
|
-
`createNativeColumnDots` is a view-independent interaction model. Connect its
|
|
32
|
-
`begin/move/end` coordinates to PanResponder or Gesture Handler. Set `removeDirection` to `up`, `down` or `either` (the renderer-neutral default). It supports
|
|
33
|
-
live field replacement along the slider, explicit reorder and toggle, sticky
|
|
34
|
-
`asc -> desc -> off` sorting, and upward tear-off.
|
|
35
|
-
|
|
36
|
-
Call `dots.dispose()` and `columns.dispose()` with the owning screen. Use
|
|
37
|
-
`columns.api.flush()` when the latest AsyncStorage write must be awaited.
|
|
1
|
+
# React Native entrypoint
|
|
2
|
+
|
|
3
|
+
Import the renderer-neutral API from `wenay-react2/native`. It has no React DOM,
|
|
4
|
+
CSS, browser-global or ag-grid dependency.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
8
|
+
import {createNativeColumnDots, createNativeColumnState} from 'wenay-react2/native'
|
|
9
|
+
|
|
10
|
+
const columns = createNativeColumnState({
|
|
11
|
+
key: 'super-admin.columns',
|
|
12
|
+
columns: [
|
|
13
|
+
{key: 'server', title: 'Server', fixed: true},
|
|
14
|
+
{key: 'status', title: 'Status'},
|
|
15
|
+
{key: 'panic', title: 'Panic'},
|
|
16
|
+
],
|
|
17
|
+
storage: AsyncStorage,
|
|
18
|
+
})
|
|
19
|
+
await columns.ready
|
|
20
|
+
|
|
21
|
+
const dots = createNativeColumnDots({state: columns, max: 8})
|
|
22
|
+
dots.begin('status', 40, 0)
|
|
23
|
+
dots.move(120, 0, {start: 0, length: 240})
|
|
24
|
+
dots.end()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The persisted `v/order/visible/width/sort/filter/groups` shape matches the web
|
|
28
|
+
`ColumnsConfig`. Writes are serialized and local edits made while hydration is
|
|
29
|
+
pending win over stale storage.
|
|
30
|
+
|
|
31
|
+
`createNativeColumnDots` is a view-independent interaction model. Connect its
|
|
32
|
+
`begin/move/end` coordinates to PanResponder or Gesture Handler. Set `removeDirection` to `up`, `down` or `either` (the renderer-neutral default). It supports
|
|
33
|
+
live field replacement along the slider, explicit reorder and toggle, sticky
|
|
34
|
+
`asc -> desc -> off` sorting, and upward tear-off.
|
|
35
|
+
|
|
36
|
+
Call `dots.dispose()` and `columns.dispose()` with the owning screen. Use
|
|
37
|
+
`columns.api.flush()` when the latest AsyncStorage write must be awaited.
|
package/doc/progress/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# Task progress
|
|
2
|
-
|
|
3
|
-
This directory is for short-lived checkpoint files during active non-trivial tasks.
|
|
4
|
-
|
|
5
|
-
Rules:
|
|
6
|
-
- create one file per task, named `doc/progress/<task>.md`;
|
|
7
|
-
- keep it short: goal, checkpoints, decisions, blockers, and verification;
|
|
8
|
-
- update it when checkpoints are completed or before switching context;
|
|
9
|
-
- delete it when the task is finished;
|
|
10
|
-
- keep durable history elsewhere: changelog/release notes for publishable changes,
|
|
11
|
-
roadmap/recommendations/target docs for long-lived project direction,
|
|
12
|
-
and the commit message or final response for the immediate summary.
|
|
13
|
-
|
|
1
|
+
# Task progress
|
|
2
|
+
|
|
3
|
+
This directory is for short-lived checkpoint files during active non-trivial tasks.
|
|
4
|
+
|
|
5
|
+
Rules:
|
|
6
|
+
- create one file per task, named `doc/progress/<task>.md`;
|
|
7
|
+
- keep it short: goal, checkpoints, decisions, blockers, and verification;
|
|
8
|
+
- update it when checkpoints are completed or before switching context;
|
|
9
|
+
- delete it when the task is finished;
|
|
10
|
+
- keep durable history elsewhere: changelog/release notes for publishable changes,
|
|
11
|
+
roadmap/recommendations/target docs for long-lived project direction,
|
|
12
|
+
and the commit message or final response for the immediate summary.
|
|
13
|
+
|
|
14
14
|
Progress files are working notes, not API docs, release notes, or the durable target queue. When a task comes from `doc/target/my.md`, update that file before deleting the progress file: move completed work to `Verify` until checks pass, or remove it only after verification is recorded.
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
# Architecture fix queue (A1-A10) — progress
|
|
2
|
-
|
|
3
|
-
Status: in progress (session 2026-07-10). Queue source: `doc/target/my.md` (audit 2026-07-09).
|
|
4
|
-
Durable per-change record: `doc/changes/v1.0.41.md` (shipped part), `doc/changes/v1.0.42.md` (current).
|
|
5
|
-
|
|
6
|
-
## Session 2026-07-10 — recovery + tail of the queue
|
|
7
|
-
|
|
8
|
-
Context: the machine rebooted; recovered from docs. Discovered and fixed first:
|
|
9
|
-
the 1.0.41 release commit did NOT contain `columnGrid.tsx`, `fixedOrder.ts`,
|
|
10
|
-
`persistedMaps.ts` and the whole architecture pass (npm had them, git did not —
|
|
11
|
-
same failure mode as the earlier eb4e9b3 fix). Also `.npmrc` with a live npm
|
|
12
|
-
auth token was sitting untracked — now gitignored (`/.npmrc`, `.vite-*.log`);
|
|
13
|
-
the token itself should probably be rotated since it was on disk in the repo dir.
|
|
14
|
-
|
|
15
|
-
Commits this session (chronological):
|
|
16
|
-
|
|
17
|
-
1. **Post-release commit of the 1.0.41 working tree** — createColumnGrid sources,
|
|
18
|
-
A1/A2/A4/A5/A6/A8/A10-safe, hook-extraction do-now trio, common2 ^1.0.70,
|
|
19
|
-
changelog/docs; .gitignore hardening.
|
|
20
|
-
2. **A10 safe part 2** — `onClickClose` alias (+deprecated `onCLickClose`),
|
|
21
|
-
`keyForSave` alias on `Button` (+deprecated `keySave`), QA card dup fix
|
|
22
|
-
(Replay 25→33, 26→34), `__test/tokens.test.ts` (two-way tokens.ts↔tokens.css).
|
|
23
|
-
Verify: tsc qa-check, jest 47/47.
|
|
24
|
-
3. **A10 loop-guard** — `utils/structEqual.ts` (+barrel export, permanent test);
|
|
25
|
-
`columnState.readFromGrid` guards off `JSON.stringify`; order-sensitive
|
|
26
|
-
`sameMap` deliberately untouched (its sensitivity re-imposes stored order on
|
|
27
|
-
columnDefs reset). Verify: tsc, jest 53/53.
|
|
28
|
-
4. **A7** — `DragBox` → thin adapter over `useDraggableApi` (+additive
|
|
29
|
-
`onMove`/`trackState:false` on the hook); `DragArea` @deprecated as-is;
|
|
30
|
-
`useFloatingWindowController`/`StickerMenu` deliberately stay bespoke
|
|
31
|
-
(recon: clamp, `buttons===1` recovery, persist, z-stack / mount-lifetime
|
|
32
|
-
listeners, click-suppression are load-bearing). `__test/dragBox.test.tsx`,
|
|
33
|
-
QA card 35. Verify: tsc, jest 58/58, stand: card 35 two drags commit without
|
|
34
|
-
jump-back (renders 1+2/gesture), card 26 reorder commits once, zero console
|
|
35
|
-
errors on fresh load.
|
|
36
|
-
|
|
37
|
-
## A9 — DONE (committed 2026-07-10, 5th commit of the session; build OK)
|
|
38
|
-
|
|
39
|
-
Done:
|
|
40
|
-
|
|
41
|
-
- `components/Overlay.tsx` — INTERNAL (not exported) portal+scrim+outside+Escape
|
|
42
|
-
composition; outside-click delegates to `OutsideClickArea`; Escape listener only
|
|
43
|
-
when `onEscape` given; callbacks through refs. Doubles as the DOM seam for the
|
|
44
|
-
future react-native view layer (headless state stays in hosts).
|
|
45
|
-
- `ModalProvider` → Overlay (inline token scrim + flex centering, Escape gated by
|
|
46
|
-
`closeOnEscape`, outside by `closeOnOutsideClick`) — public API untouched.
|
|
47
|
-
- `SettingsDialog` → Overlay (`wenayDlgScrim`/`wenayDlgOutside` classes,
|
|
48
|
-
NO onEscape — the controller keeps the two-stage Escape: clear search, then close).
|
|
49
|
-
- New permanent `__test/modalProvider.test.tsx` (portal/scrim/Escape/outside gates).
|
|
50
|
-
|
|
51
|
-
Verified so far: tsc qa-check OK; jest 62/62; stand card 13 — open, Escape,
|
|
52
|
-
outside click, close button all work (scrim dims, token z-index); card 20 —
|
|
53
|
-
open, two-stage Escape (clear → close), outside click, window x. Console clean
|
|
54
|
-
on fresh load (the ReferenceError seen mid-session was a vite HMR intermediate
|
|
55
|
-
between two sequential edits, gone after reload).
|
|
56
|
-
|
|
57
|
-
Deliberately NOT overlaid (recorded in changelog): `createModalElementStore`
|
|
58
|
-
(render-slot store, not chrome), `LeftModal`/`Modal2` drawer (gesture-driven,
|
|
59
|
-
no scrim), `ModalWrapper`/Input helpers (backdrop-less by design, hosted inside
|
|
60
|
-
another modal slot).
|
|
61
|
-
|
|
62
|
-
Close-out complete: fresh-page card-13/20 spot checks passed, changelog +
|
|
63
|
-
my.md updated, `npm run build` OK, committed.
|
|
64
|
-
|
|
65
|
-
## Queue after this session
|
|
66
|
-
|
|
67
|
-
- **A3 (BREAKING)** dead `menuR.tsx` removal — only in a deliberate breaking version.
|
|
68
|
-
- **A10 breaking leftovers** — raw `ListenApi` narrowing; removal of deprecated
|
|
69
|
-
`onCLickClose`/`keySave`/`DragArea`.
|
|
70
|
-
- Then Ready queue: common2 1.0.66-1.0.70 adoption (useMediaSource, Peer SDK
|
|
71
|
-
adapter, WebRTC recipe), context-menu controller split, chart canvas wrapper.
|
|
72
|
-
- New Inbox item 2026-07-10: React Native / mobile version of the library's
|
|
73
|
-
functionality (see `doc/target/my.md` Inbox + verbatim dictation) — A9 Overlay
|
|
74
|
-
was already shaped with that in mind (DOM isolated in one leaf).
|
|
1
|
+
# Architecture fix queue (A1-A10) — progress
|
|
2
|
+
|
|
3
|
+
Status: in progress (session 2026-07-10). Queue source: `doc/target/my.md` (audit 2026-07-09).
|
|
4
|
+
Durable per-change record: `doc/changes/v1.0.41.md` (shipped part), `doc/changes/v1.0.42.md` (current).
|
|
5
|
+
|
|
6
|
+
## Session 2026-07-10 — recovery + tail of the queue
|
|
7
|
+
|
|
8
|
+
Context: the machine rebooted; recovered from docs. Discovered and fixed first:
|
|
9
|
+
the 1.0.41 release commit did NOT contain `columnGrid.tsx`, `fixedOrder.ts`,
|
|
10
|
+
`persistedMaps.ts` and the whole architecture pass (npm had them, git did not —
|
|
11
|
+
same failure mode as the earlier eb4e9b3 fix). Also `.npmrc` with a live npm
|
|
12
|
+
auth token was sitting untracked — now gitignored (`/.npmrc`, `.vite-*.log`);
|
|
13
|
+
the token itself should probably be rotated since it was on disk in the repo dir.
|
|
14
|
+
|
|
15
|
+
Commits this session (chronological):
|
|
16
|
+
|
|
17
|
+
1. **Post-release commit of the 1.0.41 working tree** — createColumnGrid sources,
|
|
18
|
+
A1/A2/A4/A5/A6/A8/A10-safe, hook-extraction do-now trio, common2 ^1.0.70,
|
|
19
|
+
changelog/docs; .gitignore hardening.
|
|
20
|
+
2. **A10 safe part 2** — `onClickClose` alias (+deprecated `onCLickClose`),
|
|
21
|
+
`keyForSave` alias on `Button` (+deprecated `keySave`), QA card dup fix
|
|
22
|
+
(Replay 25→33, 26→34), `__test/tokens.test.ts` (two-way tokens.ts↔tokens.css).
|
|
23
|
+
Verify: tsc qa-check, jest 47/47.
|
|
24
|
+
3. **A10 loop-guard** — `utils/structEqual.ts` (+barrel export, permanent test);
|
|
25
|
+
`columnState.readFromGrid` guards off `JSON.stringify`; order-sensitive
|
|
26
|
+
`sameMap` deliberately untouched (its sensitivity re-imposes stored order on
|
|
27
|
+
columnDefs reset). Verify: tsc, jest 53/53.
|
|
28
|
+
4. **A7** — `DragBox` → thin adapter over `useDraggableApi` (+additive
|
|
29
|
+
`onMove`/`trackState:false` on the hook); `DragArea` @deprecated as-is;
|
|
30
|
+
`useFloatingWindowController`/`StickerMenu` deliberately stay bespoke
|
|
31
|
+
(recon: clamp, `buttons===1` recovery, persist, z-stack / mount-lifetime
|
|
32
|
+
listeners, click-suppression are load-bearing). `__test/dragBox.test.tsx`,
|
|
33
|
+
QA card 35. Verify: tsc, jest 58/58, stand: card 35 two drags commit without
|
|
34
|
+
jump-back (renders 1+2/gesture), card 26 reorder commits once, zero console
|
|
35
|
+
errors on fresh load.
|
|
36
|
+
|
|
37
|
+
## A9 — DONE (committed 2026-07-10, 5th commit of the session; build OK)
|
|
38
|
+
|
|
39
|
+
Done:
|
|
40
|
+
|
|
41
|
+
- `components/Overlay.tsx` — INTERNAL (not exported) portal+scrim+outside+Escape
|
|
42
|
+
composition; outside-click delegates to `OutsideClickArea`; Escape listener only
|
|
43
|
+
when `onEscape` given; callbacks through refs. Doubles as the DOM seam for the
|
|
44
|
+
future react-native view layer (headless state stays in hosts).
|
|
45
|
+
- `ModalProvider` → Overlay (inline token scrim + flex centering, Escape gated by
|
|
46
|
+
`closeOnEscape`, outside by `closeOnOutsideClick`) — public API untouched.
|
|
47
|
+
- `SettingsDialog` → Overlay (`wenayDlgScrim`/`wenayDlgOutside` classes,
|
|
48
|
+
NO onEscape — the controller keeps the two-stage Escape: clear search, then close).
|
|
49
|
+
- New permanent `__test/modalProvider.test.tsx` (portal/scrim/Escape/outside gates).
|
|
50
|
+
|
|
51
|
+
Verified so far: tsc qa-check OK; jest 62/62; stand card 13 — open, Escape,
|
|
52
|
+
outside click, close button all work (scrim dims, token z-index); card 20 —
|
|
53
|
+
open, two-stage Escape (clear → close), outside click, window x. Console clean
|
|
54
|
+
on fresh load (the ReferenceError seen mid-session was a vite HMR intermediate
|
|
55
|
+
between two sequential edits, gone after reload).
|
|
56
|
+
|
|
57
|
+
Deliberately NOT overlaid (recorded in changelog): `createModalElementStore`
|
|
58
|
+
(render-slot store, not chrome), `LeftModal`/`Modal2` drawer (gesture-driven,
|
|
59
|
+
no scrim), `ModalWrapper`/Input helpers (backdrop-less by design, hosted inside
|
|
60
|
+
another modal slot).
|
|
61
|
+
|
|
62
|
+
Close-out complete: fresh-page card-13/20 spot checks passed, changelog +
|
|
63
|
+
my.md updated, `npm run build` OK, committed.
|
|
64
|
+
|
|
65
|
+
## Queue after this session
|
|
66
|
+
|
|
67
|
+
- **A3 (BREAKING)** dead `menuR.tsx` removal — only in a deliberate breaking version.
|
|
68
|
+
- **A10 breaking leftovers** — raw `ListenApi` narrowing; removal of deprecated
|
|
69
|
+
`onCLickClose`/`keySave`/`DragArea`.
|
|
70
|
+
- Then Ready queue: common2 1.0.66-1.0.70 adoption (useMediaSource, Peer SDK
|
|
71
|
+
adapter, WebRTC recipe), context-menu controller split, chart canvas wrapper.
|
|
72
|
+
- New Inbox item 2026-07-10: React Native / mobile version of the library's
|
|
73
|
+
functionality (see `doc/target/my.md` Inbox + verbatim dictation) — A9 Overlay
|
|
74
|
+
was already shaped with that in mind (DOM isolated in one leaf).
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
# ColumnState present-gate — progress
|
|
2
|
-
|
|
3
|
-
## Scope
|
|
4
|
-
|
|
5
|
-
Fix the v1.0.43 regression reported by a consumer: a grid event must not persist
|
|
6
|
-
`visible=false` for a column that is hidden only by the runtime `presentGate`.
|
|
7
|
-
|
|
8
|
-
## Contract
|
|
9
|
-
|
|
10
|
-
`presentGate` is application-owned runtime presence. `visible` is user-owned,
|
|
11
|
-
persisted configuration. A grid readback may update visibility only when the
|
|
12
|
-
column passes the gate; order, width, sort and filter continue to read back for
|
|
13
|
-
all known columns.
|
|
14
|
-
|
|
15
|
-
## Plan
|
|
16
|
-
|
|
17
|
-
1. Reproduce against current source with a fake grid. **Confirmed:** an unrelated
|
|
18
|
-
resize changed gated `b` from persisted `visible=true` to `false`.
|
|
19
|
-
2. Guard the visibility fold in `readFromGrid` with `passesPresentGate`. **Done.**
|
|
20
|
-
3. Add regression coverage for closed and open gates. **Done:** fake GridApi
|
|
21
|
-
covers both the protected gated column and a normal open-gate visibility edit.
|
|
22
|
-
4. Run TypeScript, Jest and package build; record release notes. **Done:**
|
|
23
|
-
targeted Jest 3/3, full Jest 64/64, qa-check TypeScript and build passed.
|
|
24
|
-
|
|
25
|
-
## Result
|
|
26
|
-
|
|
27
|
-
Published as `wenay-react2@1.0.44`; durable details are in
|
|
28
|
-
`doc/changes/v1.0.44.md`.
|
|
1
|
+
# ColumnState present-gate — progress
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
Fix the v1.0.43 regression reported by a consumer: a grid event must not persist
|
|
6
|
+
`visible=false` for a column that is hidden only by the runtime `presentGate`.
|
|
7
|
+
|
|
8
|
+
## Contract
|
|
9
|
+
|
|
10
|
+
`presentGate` is application-owned runtime presence. `visible` is user-owned,
|
|
11
|
+
persisted configuration. A grid readback may update visibility only when the
|
|
12
|
+
column passes the gate; order, width, sort and filter continue to read back for
|
|
13
|
+
all known columns.
|
|
14
|
+
|
|
15
|
+
## Plan
|
|
16
|
+
|
|
17
|
+
1. Reproduce against current source with a fake grid. **Confirmed:** an unrelated
|
|
18
|
+
resize changed gated `b` from persisted `visible=true` to `false`.
|
|
19
|
+
2. Guard the visibility fold in `readFromGrid` with `passesPresentGate`. **Done.**
|
|
20
|
+
3. Add regression coverage for closed and open gates. **Done:** fake GridApi
|
|
21
|
+
covers both the protected gated column and a normal open-gate visibility edit.
|
|
22
|
+
4. Run TypeScript, Jest and package build; record release notes. **Done:**
|
|
23
|
+
targeted Jest 3/3, full Jest 64/64, qa-check TypeScript and build passed.
|
|
24
|
+
|
|
25
|
+
## Result
|
|
26
|
+
|
|
27
|
+
Published as `wenay-react2@1.0.44`; durable details are in
|
|
28
|
+
`doc/changes/v1.0.44.md`.
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
# wenay-common2 1.0.73 adoption — progress
|
|
2
|
-
|
|
3
|
-
## Update
|
|
4
|
-
|
|
5
|
-
- Previous dependency: 1.0.70.
|
|
6
|
-
- npm latest verified: 1.0.73.
|
|
7
|
-
- Updated dependency and lockfile to 1.0.73.
|
|
8
|
-
- Compatibility: `tsc -p tsconfig.qa-check.json --noEmit` and full Jest 21 suites / 68 tests pass.
|
|
9
|
-
|
|
10
|
-
## Architecture decision
|
|
11
|
-
|
|
12
|
-
| common2 capability | wenay-react2 decision | Reason |
|
|
13
|
-
| --- | --- | --- |
|
|
14
|
-
| Peer gap repair / `resync()` (1.0.71) | Core-only protocol; future `usePeer` may expose status and explicit `resync` | React must not own journal or repair state. |
|
|
15
|
-
| Hidden-tab Media capture (1.0.72) | Adopt core defaults, no adapter | Browser worker/ImageCapture implementation belongs below React. |
|
|
16
|
-
| `Media.attachVideoCanvas` / audio player / publish pipe (1.0.73) | Recipe and QA consumer first; thin ref-lifecycle hooks only if repeated | Frame decode/playback must bypass React renders. |
|
|
17
|
-
|
|
18
|
-
## Follow-up
|
|
19
|
-
|
|
20
|
-
- Keep the existing `useMediaSource`, `usePeer` and WebRTC recipe items in target.
|
|
21
|
-
- Add a real Media/Peer QA consumer before introducing any new hook.
|
|
22
|
-
- No raw route-coordinator hook: Peer mirrors already survive route changes.
|
|
23
|
-
## Implementation batch
|
|
24
|
-
|
|
25
|
-
- Added `useMediaSource`: React owns source start/stop/device lifecycle only; frames remain on the common2 Listen path.
|
|
26
|
-
- Added `usePeer`: React exposes an SDK peer's mirrored store, low-frequency route state and explicit controls; it owns no repair or transport state.
|
|
27
|
-
- QA cards 38/39/40 cover camera canvas viewer, microphone player and an in-process Peer host/mirror/resync scenario.
|
|
28
|
-
- Verification: qa-check TypeScript, Jest 22 suites / 71 tests, package build, diff-check and live stand HTTP 200.
|
|
1
|
+
# wenay-common2 1.0.73 adoption — progress
|
|
2
|
+
|
|
3
|
+
## Update
|
|
4
|
+
|
|
5
|
+
- Previous dependency: 1.0.70.
|
|
6
|
+
- npm latest verified: 1.0.73.
|
|
7
|
+
- Updated dependency and lockfile to 1.0.73.
|
|
8
|
+
- Compatibility: `tsc -p tsconfig.qa-check.json --noEmit` and full Jest 21 suites / 68 tests pass.
|
|
9
|
+
|
|
10
|
+
## Architecture decision
|
|
11
|
+
|
|
12
|
+
| common2 capability | wenay-react2 decision | Reason |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Peer gap repair / `resync()` (1.0.71) | Core-only protocol; future `usePeer` may expose status and explicit `resync` | React must not own journal or repair state. |
|
|
15
|
+
| Hidden-tab Media capture (1.0.72) | Adopt core defaults, no adapter | Browser worker/ImageCapture implementation belongs below React. |
|
|
16
|
+
| `Media.attachVideoCanvas` / audio player / publish pipe (1.0.73) | Recipe and QA consumer first; thin ref-lifecycle hooks only if repeated | Frame decode/playback must bypass React renders. |
|
|
17
|
+
|
|
18
|
+
## Follow-up
|
|
19
|
+
|
|
20
|
+
- Keep the existing `useMediaSource`, `usePeer` and WebRTC recipe items in target.
|
|
21
|
+
- Add a real Media/Peer QA consumer before introducing any new hook.
|
|
22
|
+
- No raw route-coordinator hook: Peer mirrors already survive route changes.
|
|
23
|
+
## Implementation batch
|
|
24
|
+
|
|
25
|
+
- Added `useMediaSource`: React owns source start/stop/device lifecycle only; frames remain on the common2 Listen path.
|
|
26
|
+
- Added `usePeer`: React exposes an SDK peer's mirrored store, low-frequency route state and explicit controls; it owns no repair or transport state.
|
|
27
|
+
- QA cards 38/39/40 cover camera canvas viewer, microphone player and an in-process Peer host/mirror/resync scenario.
|
|
28
|
+
- Verification: qa-check TypeScript, Jest 22 suites / 71 tests, package build, diff-check and live stand HTTP 200.
|
|
29
29
|
- Manual Verify: run cards 38, 39, 40 together with existing replay cards 23 and 34 before release.
|
|
30
30
|
- Manual verification completed by the user on the stand; release candidate is 1.0.45.
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# wenay-common2 1.0.74 — adoption assessment
|
|
2
|
-
|
|
3
|
-
## Upgrade
|
|
4
|
-
|
|
5
|
-
- Updated the workspace lockfile from `wenay-common2` 1.0.73 to 1.0.74.
|
|
6
|
-
- Compatibility: qa-check TypeScript, full Jest (20 suites / 67 tests), package build and diff-check pass.
|
|
7
|
-
|
|
8
|
-
## Architecture decision
|
|
9
|
-
|
|
10
|
-
- `Peer.createCallManager` is a new application interaction lifecycle, not an extension of the mirrored-store `usePeer` adapter.
|
|
11
|
-
- React may bind call state and controls; common2 retains signaling envelopes, call-id ordering, busy/glare handling, timeouts and offline verdicts.
|
|
12
|
-
- Presence belongs to the host protocol (`list()` plus edge-triggered `changes`), not polling or a second React-owned store.
|
|
13
|
-
- `createMediaRelay` remains relay-first and server-authorized through `watchOf` / `canWatch`; a call UI attaches viewers only while active. React must not make ACL decisions.
|
|
14
|
-
|
|
15
|
-
## Planned validation batch
|
|
16
|
-
|
|
17
|
-
Presence edges; ring/accept/active; media keyframe; hangup/decline/offline/busy; ACL revoke of an already-open viewer.
|
|
18
|
-
## Implementation batch — 1.0.46
|
|
19
|
-
|
|
20
|
-
- Added `usePeerCalls(manager)`: a thin binding for ring/active state and explicit `call`; the app owns `manager.close()`.
|
|
21
|
-
- Added `usePeerPresence(presence)`: subscribe-first snapshot plus online/offline edge projection.
|
|
22
|
-
- QA cards 41–44 verify an in-process call lifecycle, presence disconnect/reconnect and media relay ACL revocation on an open viewer.
|
|
23
|
-
- Automated verification: qa-check TypeScript, real in-process hook tests, full Jest and package build.
|
|
24
|
-
- Manual Verify pending: QA card 43 — grant camera access, confirm canvas receives frames; revoke ACL and confirm viewer frame count freezes while source remains live; grant again and confirm it resumes. QA card 44 — grant microphone access, confirm audio player receives frames/plays; revoke and grant ACL with the same expectation.
|
|
1
|
+
# wenay-common2 1.0.74 — adoption assessment
|
|
2
|
+
|
|
3
|
+
## Upgrade
|
|
4
|
+
|
|
5
|
+
- Updated the workspace lockfile from `wenay-common2` 1.0.73 to 1.0.74.
|
|
6
|
+
- Compatibility: qa-check TypeScript, full Jest (20 suites / 67 tests), package build and diff-check pass.
|
|
7
|
+
|
|
8
|
+
## Architecture decision
|
|
9
|
+
|
|
10
|
+
- `Peer.createCallManager` is a new application interaction lifecycle, not an extension of the mirrored-store `usePeer` adapter.
|
|
11
|
+
- React may bind call state and controls; common2 retains signaling envelopes, call-id ordering, busy/glare handling, timeouts and offline verdicts.
|
|
12
|
+
- Presence belongs to the host protocol (`list()` plus edge-triggered `changes`), not polling or a second React-owned store.
|
|
13
|
+
- `createMediaRelay` remains relay-first and server-authorized through `watchOf` / `canWatch`; a call UI attaches viewers only while active. React must not make ACL decisions.
|
|
14
|
+
|
|
15
|
+
## Planned validation batch
|
|
16
|
+
|
|
17
|
+
Presence edges; ring/accept/active; media keyframe; hangup/decline/offline/busy; ACL revoke of an already-open viewer.
|
|
18
|
+
## Implementation batch — 1.0.46
|
|
19
|
+
|
|
20
|
+
- Added `usePeerCalls(manager)`: a thin binding for ring/active state and explicit `call`; the app owns `manager.close()`.
|
|
21
|
+
- Added `usePeerPresence(presence)`: subscribe-first snapshot plus online/offline edge projection.
|
|
22
|
+
- QA cards 41–44 verify an in-process call lifecycle, presence disconnect/reconnect and media relay ACL revocation on an open viewer.
|
|
23
|
+
- Automated verification: qa-check TypeScript, real in-process hook tests, full Jest and package build.
|
|
24
|
+
- Manual Verify pending: QA card 43 — grant camera access, confirm canvas receives frames; revoke ACL and confirm viewer frame count freezes while source remains live; grant again and confirm it resumes. QA card 44 — grant microphone access, confirm audio player receives frames/plays; revoke and grant ACL with the same expectation.
|