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.
Files changed (56) hide show
  1. package/README.md +14 -14
  2. package/doc/EXAMPLE_USAGE.md +969 -969
  3. package/doc/PROJECT_FUNCTIONALITY.md +403 -403
  4. package/doc/PROJECT_RULES.md +27 -27
  5. package/doc/WENAY_REACT2_RENAMES.md +170 -170
  6. package/doc/changes/v1.0.35.md +18 -18
  7. package/doc/changes/v1.0.38.md +37 -37
  8. package/doc/changes/v1.0.39.md +23 -23
  9. package/doc/changes/v1.0.40.md +14 -14
  10. package/doc/changes/v1.0.41.md +35 -35
  11. package/doc/changes/v1.0.42.md +26 -26
  12. package/doc/changes/v1.0.43.md +10 -10
  13. package/doc/changes/v1.0.44.md +14 -14
  14. package/doc/changes/v1.0.45.md +8 -8
  15. package/doc/changes/v1.0.46.md +11 -11
  16. package/doc/changes/v1.0.47.md +9 -9
  17. package/doc/changes/v1.0.48.md +8 -0
  18. package/doc/changes/v1.0.49.md +7 -0
  19. package/doc/examples/README.md +5 -4
  20. package/doc/examples/conference-client.html +34 -0
  21. package/doc/examples/conference-client.ts +212 -0
  22. package/doc/examples/conference-server.mjs +150 -0
  23. package/doc/examples/peer-call-media.tsx +7 -7
  24. package/doc/examples/stand.tsx +5 -5
  25. package/doc/native.md +37 -37
  26. package/doc/progress/README.md +13 -13
  27. package/doc/progress/architecture-fix-queue.md +74 -74
  28. package/doc/progress/column-state-present-gate.md +28 -28
  29. package/doc/progress/common2-adoption-1.0.73.md +28 -28
  30. package/doc/progress/common2-adoption-1.0.74.md +24 -24
  31. package/doc/progress/hook-controller-opportunities.md +363 -363
  32. package/doc/progress/hook-extraction-audit.md +195 -195
  33. package/doc/progress/public-surface-normalization.md +351 -351
  34. package/doc/progress/qa-stand-walkthrough-2026-07-12.md +62 -0
  35. package/doc/progress/stand-as-examples-audit.md +20 -20
  36. package/doc/progress/style-system-normalization.md +121 -121
  37. package/doc/target/README.md +32 -32
  38. package/doc/target/my.md +134 -124
  39. package/doc/wenay-react2-rare.md +807 -807
  40. package/doc/wenay-react2.md +543 -541
  41. package/lib/common/demo/fakeRtcLoopback.d.ts +17 -0
  42. package/lib/common/demo/fakeRtcLoopback.js +108 -0
  43. package/lib/common/demo/peerConference.d.ts +449 -0
  44. package/lib/common/demo/peerConference.js +372 -0
  45. package/lib/common/demo/peerMedia.js +6 -1
  46. package/lib/common/src/hooks/index.d.ts +1 -0
  47. package/lib/common/src/hooks/index.js +1 -0
  48. package/lib/common/src/hooks/useRoute.d.ts +28 -0
  49. package/lib/common/src/hooks/useRoute.js +30 -0
  50. package/lib/common/src/logs/logsController.js +2 -2
  51. package/lib/common/testUseReact/qa.js +4 -3
  52. package/lib/style/menuRight.css +19 -19
  53. package/lib/style/style.css +23 -23
  54. package/lib/style/tokens.css +184 -184
  55. package/package.json +2 -1
  56. package/doc/wenay-react2-1.0.8.tgz +0 -0
@@ -1,15 +1,15 @@
1
- # v1.0.40
2
-
3
- Date: 2026-07-09
4
-
5
- - Changed: Added FLIP-style item movement animation to the standard `createToolbar().Bar`, matching the card 30 behavior for horizontal toolbar menus.
6
- - Changed: QA cards 25 and 31 now receive toolbar item glide animation through the shared `Toolbar.Bar`; card 30 remains the stand-only visual reference.
7
- - Changed: Added `useFloatingWindowController` plus public geometry/controller types as the first controller layer for `FloatingWindow` saved geometry, z-index stack, drag state, resize callbacks, and viewport clamp.
8
- - Changed: Added controller-first layers for MiniLogs, RightMenu, ParamsEditor, Logs, SettingsDialog, and context logger UI wrappers while keeping compatibility components.
9
- - Changed: Added explicit `MenuItemStrict.actionKey` and action-level `contextMenu.stats` counters.
10
- - Changed: Moved log tables and context log table to shared `AgGridTable` primitives where behavior was safe to preserve.
11
- - Changed: Updated QA card 25 toolbar subscription to use `useListenEffect(tb.api.onChange, ...)` instead of a manual `useEffect(() => listen.on(...))` cleanup.
12
- - Fixed: `SettingsDialog` search history closes when focus leaves the search box.
13
- - Skipped: `FResizableReact` hook, `memoryStore` core rewrite, broad `DragBox` rewrite, `PageVisibilityProvider` adoption, and `logsContext` persistence migration until a real consumer/test scenario exists.
14
- - Note: The QA stand mounts log tables in card 9; top-right log notification layers exist in the library but are not mounted globally in the stand.
1
+ # v1.0.40
2
+
3
+ Date: 2026-07-09
4
+
5
+ - Changed: Added FLIP-style item movement animation to the standard `createToolbar().Bar`, matching the card 30 behavior for horizontal toolbar menus.
6
+ - Changed: QA cards 25 and 31 now receive toolbar item glide animation through the shared `Toolbar.Bar`; card 30 remains the stand-only visual reference.
7
+ - Changed: Added `useFloatingWindowController` plus public geometry/controller types as the first controller layer for `FloatingWindow` saved geometry, z-index stack, drag state, resize callbacks, and viewport clamp.
8
+ - Changed: Added controller-first layers for MiniLogs, RightMenu, ParamsEditor, Logs, SettingsDialog, and context logger UI wrappers while keeping compatibility components.
9
+ - Changed: Added explicit `MenuItemStrict.actionKey` and action-level `contextMenu.stats` counters.
10
+ - Changed: Moved log tables and context log table to shared `AgGridTable` primitives where behavior was safe to preserve.
11
+ - Changed: Updated QA card 25 toolbar subscription to use `useListenEffect(tb.api.onChange, ...)` instead of a manual `useEffect(() => listen.on(...))` cleanup.
12
+ - Fixed: `SettingsDialog` search history closes when focus leaves the search box.
13
+ - Skipped: `FResizableReact` hook, `memoryStore` core rewrite, broad `DragBox` rewrite, `PageVisibilityProvider` adoption, and `logsContext` persistence migration until a real consumer/test scenario exists.
14
+ - Note: The QA stand mounts log tables in card 9; top-right log notification layers exist in the library but are not mounted globally in the stand.
15
15
  - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand`; `npm run build`; `git diff --check`.
@@ -1,36 +1,36 @@
1
- # v1.0.41
2
-
3
- Date: 2026-07-09
4
-
5
- Status: released.
6
-
7
- - Changed: Split global log notifications into `useMessageEventLogsController`, `MessageEventLogsView`, and `MessageEventLogCard`; `MessageEventLogs` and `logsApi.React.Message` remain compatibility wrappers.
8
- - Changed: QA card 9 now mounts the global log notification layer, so the corner popups can be checked from the stand with the existing `add log` button.
9
- - Changed: Added usage docs for the hook/controller notification layer and recorded follow-up hook/controller split candidates with effectiveness/simplicity estimates.
10
- - Fixed: npm package assembly now copies `doc/` into `dist` and allows `doc/**/*` in package `files`; pack verification confirms `doc/changes/v1.0.41.md`, `doc/wenay-react2.md`, and `doc/wenay-react2-rare.md` are included.
11
- - Fixed: Restored opaque dark default styling for mouse/right-click menus; hover is white with dark text, active stays dark, and pressed state has a separate contrast token.
12
- - Changed: Polished the log notification toggle in card 9 / `MessageEventLogsView` from a large text `X` into a compact close button.
13
- - Added: `createColumnGrid` / `useColumnGrid`, a high-level column kit that infers `ColumnMeta` from ag-grid `columnDefs`, accepts optional default `data`/`getId`, and returns ready table, menu, dots, cards, toolbar, settings, and view surfaces.
14
- - Changed: Added QA card 32 for the default grid-menu wrapper, including the built-in dots overlay driving both table and card representations.
15
- - Changed: `createColumnGrid.View` now defaults `controls="auto"` to the dots overlay; `Dots` defaults to all columns, default toolbar items toggle column visibility, and `autoSizeOnColumnCountChange` optionally fits only when visible column count changes.
16
- - Changed: Updated `wenay-common2` from `^1.0.65` to `^1.0.70`.
17
- - Reason: common2 1.0.66 adds the `Media` namespace (browser mic/camera capture as binary `Listen` sources, fixed-header media frames via `encodeMediaFrame`/`decodeMediaFrame`, `replay:true` media mode in `createRpcServerAuto`, `wenay-common2/media` export). Media lines are ordinary Listen/replay lines, so existing `useListen*`/`useReplay*` hooks consume them without new React surface; the brief/rare replay docs now record this instead of adding hooks.
18
- - Reason: common2 1.0.67 adds `Replay.createRouteCoordinator` (policy-gated relay<->direct routing over pure `RouteConnector` transports; data continuity via `replayRouteSubscribe`). Coordinator `link.subscribe` returns the same handle shape as our route hooks (`ready, seq(), label(), active()`); a React adapter for coordinator links is recorded as a target goal, existing route hooks stay the manual `switchRoute` surface.
19
- - Reason: common2 1.0.68 completes the direct path: `Replay.createSignalHub` (WebRTC signaling over the existing socket/RPC control channel), `Replay.createWebRtcConnector` (the direct `RouteConnector`; `rtc` is an injected runtime factory — in the browser `() => new RTCPeerConnection(cfg)`, which is exactly this library's environment), `acceptWebRtcDirect`, and `serveReplayChannel`/`channelReplayRemote` (replay wire over any ordered channel). Together 1.0.66-1.0.68 form the media -> replay line -> policy-routed relay/direct stack; React-side adoption goals are recorded in `doc/target/my.md`.
20
- - Reason: common2 1.0.69 adds the `Peer` namespace (`wenay-common2/peer`) — the one-call SDK over rpc + store + replay + route coordinator (`createPeerHost` / `createPeerClient` / `createPatchRelayJournal`; per-peer mirrored stores survive relay<->direct hand-offs in one seq space) and fixes real-browser ICE candidate serialization. This reframes our coordinator-adapter target goal: the React surface should sit on `Peer` (peer store mirror + route control), not on raw coordinator links. 1.0.70 ships oracle suites and the demo stand inside the npm package as living examples.
21
- - Added: `useCacheMapPersistence(cache, delay?)` in `utils/cache.ts` (+ exported `CacheMap` type) - the documented load + onDirty->saveDebounced app contract as one hook returning `{isDirty, flush, save, reload}`; the three duplicated effects in QA cards 20/21/25 now use it.
22
- - Added: `useResizeObserver(onResize)` / `useElementSize()` in `components/MyResizeObserver.tsx` - hook-shaped entry over the existing `CResizeObserver` singleton (callback ref, cb through a ref, rounded equality-guarded width/height state + live `getSize()`); `setResizeableElement` path untouched; QA card 19 migrated off its manual `ResizeObserver`+rAF wiring.
23
- - Added: `useLogsPageTable()` -> `LogsPageTableController` in `logs/logs.tsx` - the full-page logs table moved to the controller pattern (mount-time row snapshot, `applyTransactionAsync` appends, importance filter deduplicated into one `applyImportanceFilter` method); `PageLogs` is now a thin wrapper over `table.gridProps`.
24
- - Reason: the three do-now candidates from the hook-extraction audit (`doc/progress/hook-extraction-audit.md`): each removes real imperative duplication and has a live QA consumer; old names stay compatibility wrappers, public API unchanged.
25
- - Changed: Architecture audit (2026-07-09, 4 subsystem readers + core reading) recorded: fix queue in `doc/target/my.md` (A1-A10: memoryStore layering inversion, getLogsApi shared state, dead menuR, columnState barrel pulling ag-grid runtime, quadruplicated pinFixed idiom, updateBy resubscribe/ordering, drag primitive x4, undisposed onChange subscriptions, triple modal systems, naming/SSR pack); dead/suspicious surfaces added to the rare-doc Cleanup Inventory. No code changed by the audit itself.
26
- - Changed (A1): persisted maps (`floatingWindowMap`, `mapResiReact`, `mapRightMenu`) moved to the new utils leaf `utils/persistedMaps.ts`; FloatingWindow/Resizable/RightMenuStore now import and re-export them (public surface unchanged), `memoryStore` no longer imports the component layer - the utils->components backward edge is gone; `api.tsx` layer comments corrected.
27
- - Changed (A4): `columnState/index.ts` barrel is ag-grid-free again - `createColumnGrid` (AgGridTable + createToolbar runtime) is exported from the root api via its own module, grid-less consumers can import the columnState barrel directly.
28
- - Changed (A2): `getLogsApi<T>()` builds fresh per-call state (own map/mini/settings; optional `settingsKey` in `LogsApiOptions` persists instance settings); the global `logsApi` explicitly injects the legacy shared module state, so all existing consumers and QA card 9 are behavior-identical; React views bound per instance via optional state props defaulting to the legacy state.
29
- - Changed (A5): the quadruplicated `filter(!fixed)`+pinFixed idiom (columnState.normalize, ColumnsMenu.movedOrder, Toolbar.normalize, Toolbar.Settings.movedOrder) now shares `utils/fixedOrder.ts` (`pinFixedOrder`, `movedOrderWithFixed`), exported from the utils barrel; dead `renderBy`/`updateBy` imports dropped from columnState.
30
- - Changed (A6): `updateBy` - imperative `f` goes through a ref (inline callbacks no longer resubscribe every render); React notifiers and imperative callbacks live in separate sets, so `renderByLast`/`renderByRevers` affect ONLY React subscribers while `f` callbacks always run in subscription order (before React notifiers), matching the documented contract; reentrant `renderBy` on the same object coalesces (bounded passes).
31
- - Changed (A8): `createToolbar().api.dispose()` and `createColumnGrid().dispose()` release factory-lifetime subscriptions (external-source onChange, config onChange, pending fit RAF) - repeated factories over one key no longer accumulate permanent listeners; `createColumnGrid` re-seeds `visibleCount` on grid re-attach so the auto-fit is not mis-skipped after a remount.
32
- - Changed (A10, safe part): `searchHistory.items`/`use()` no longer mutate persisted state on read (normalization moved to the write path); `window` reads guarded in `RightMenu` render clamp, `LeftModal.useViewport`, and `ApiLeftMenu`'s import-time default element (SSR/tests).
33
- - Verification (architecture pass): `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (45/45); `npm run build`; QA stand 3010 fresh load with zero console errors; spot checks - card 21 place persisted across reload (persistedMaps path), card 25 toolbar click updates last action (fixedOrder + updateBy path), card 32 table/cards switch + dots overlay, card 9 add-log appends rows (isolated getLogsApi, global path).
34
- - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (45/45); `npm run build`; QA stand 3010: card 21 place switch survives F5 (hook persistence), card 19 fixed-parent 150->240px updates select 92->180px and container 150x51->240x51 via `useElementSize`, card 9 add-log appends rows through the controller.
35
- - Verification: `npx tsc --noEmit`; `npm run build`; QA stand `http://127.0.0.1:3010/` and `/src/testReact.tsx` return HTTP 200.
1
+ # v1.0.41
2
+
3
+ Date: 2026-07-09
4
+
5
+ Status: released.
6
+
7
+ - Changed: Split global log notifications into `useMessageEventLogsController`, `MessageEventLogsView`, and `MessageEventLogCard`; `MessageEventLogs` and `logsApi.React.Message` remain compatibility wrappers.
8
+ - Changed: QA card 9 now mounts the global log notification layer, so the corner popups can be checked from the stand with the existing `add log` button.
9
+ - Changed: Added usage docs for the hook/controller notification layer and recorded follow-up hook/controller split candidates with effectiveness/simplicity estimates.
10
+ - Fixed: npm package assembly now copies `doc/` into `dist` and allows `doc/**/*` in package `files`; pack verification confirms `doc/changes/v1.0.41.md`, `doc/wenay-react2.md`, and `doc/wenay-react2-rare.md` are included.
11
+ - Fixed: Restored opaque dark default styling for mouse/right-click menus; hover is white with dark text, active stays dark, and pressed state has a separate contrast token.
12
+ - Changed: Polished the log notification toggle in card 9 / `MessageEventLogsView` from a large text `X` into a compact close button.
13
+ - Added: `createColumnGrid` / `useColumnGrid`, a high-level column kit that infers `ColumnMeta` from ag-grid `columnDefs`, accepts optional default `data`/`getId`, and returns ready table, menu, dots, cards, toolbar, settings, and view surfaces.
14
+ - Changed: Added QA card 32 for the default grid-menu wrapper, including the built-in dots overlay driving both table and card representations.
15
+ - Changed: `createColumnGrid.View` now defaults `controls="auto"` to the dots overlay; `Dots` defaults to all columns, default toolbar items toggle column visibility, and `autoSizeOnColumnCountChange` optionally fits only when visible column count changes.
16
+ - Changed: Updated `wenay-common2` from `^1.0.65` to `^1.0.70`.
17
+ - Reason: common2 1.0.66 adds the `Media` namespace (browser mic/camera capture as binary `Listen` sources, fixed-header media frames via `encodeMediaFrame`/`decodeMediaFrame`, `replay:true` media mode in `createRpcServerAuto`, `wenay-common2/media` export). Media lines are ordinary Listen/replay lines, so existing `useListen*`/`useReplay*` hooks consume them without new React surface; the brief/rare replay docs now record this instead of adding hooks.
18
+ - Reason: common2 1.0.67 adds `Replay.createRouteCoordinator` (policy-gated relay<->direct routing over pure `RouteConnector` transports; data continuity via `replayRouteSubscribe`). Coordinator `link.subscribe` returns the same handle shape as our route hooks (`ready, seq(), label(), active()`); a React adapter for coordinator links is recorded as a target goal, existing route hooks stay the manual `switchRoute` surface.
19
+ - Reason: common2 1.0.68 completes the direct path: `Replay.createSignalHub` (WebRTC signaling over the existing socket/RPC control channel), `Replay.createWebRtcConnector` (the direct `RouteConnector`; `rtc` is an injected runtime factory — in the browser `() => new RTCPeerConnection(cfg)`, which is exactly this library's environment), `acceptWebRtcDirect`, and `serveReplayChannel`/`channelReplayRemote` (replay wire over any ordered channel). Together 1.0.66-1.0.68 form the media -> replay line -> policy-routed relay/direct stack; React-side adoption goals are recorded in `doc/target/my.md`.
20
+ - Reason: common2 1.0.69 adds the `Peer` namespace (`wenay-common2/peer`) — the one-call SDK over rpc + store + replay + route coordinator (`createPeerHost` / `createPeerClient` / `createPatchRelayJournal`; per-peer mirrored stores survive relay<->direct hand-offs in one seq space) and fixes real-browser ICE candidate serialization. This reframes our coordinator-adapter target goal: the React surface should sit on `Peer` (peer store mirror + route control), not on raw coordinator links. 1.0.70 ships oracle suites and the demo stand inside the npm package as living examples.
21
+ - Added: `useCacheMapPersistence(cache, delay?)` in `utils/cache.ts` (+ exported `CacheMap` type) - the documented load + onDirty->saveDebounced app contract as one hook returning `{isDirty, flush, save, reload}`; the three duplicated effects in QA cards 20/21/25 now use it.
22
+ - Added: `useResizeObserver(onResize)` / `useElementSize()` in `components/MyResizeObserver.tsx` - hook-shaped entry over the existing `CResizeObserver` singleton (callback ref, cb through a ref, rounded equality-guarded width/height state + live `getSize()`); `setResizeableElement` path untouched; QA card 19 migrated off its manual `ResizeObserver`+rAF wiring.
23
+ - Added: `useLogsPageTable()` -> `LogsPageTableController` in `logs/logs.tsx` - the full-page logs table moved to the controller pattern (mount-time row snapshot, `applyTransactionAsync` appends, importance filter deduplicated into one `applyImportanceFilter` method); `PageLogs` is now a thin wrapper over `table.gridProps`.
24
+ - Reason: the three do-now candidates from the hook-extraction audit (`doc/progress/hook-extraction-audit.md`): each removes real imperative duplication and has a live QA consumer; old names stay compatibility wrappers, public API unchanged.
25
+ - Changed: Architecture audit (2026-07-09, 4 subsystem readers + core reading) recorded: fix queue in `doc/target/my.md` (A1-A10: memoryStore layering inversion, getLogsApi shared state, dead menuR, columnState barrel pulling ag-grid runtime, quadruplicated pinFixed idiom, updateBy resubscribe/ordering, drag primitive x4, undisposed onChange subscriptions, triple modal systems, naming/SSR pack); dead/suspicious surfaces added to the rare-doc Cleanup Inventory. No code changed by the audit itself.
26
+ - Changed (A1): persisted maps (`floatingWindowMap`, `mapResiReact`, `mapRightMenu`) moved to the new utils leaf `utils/persistedMaps.ts`; FloatingWindow/Resizable/RightMenuStore now import and re-export them (public surface unchanged), `memoryStore` no longer imports the component layer - the utils->components backward edge is gone; `api.tsx` layer comments corrected.
27
+ - Changed (A4): `columnState/index.ts` barrel is ag-grid-free again - `createColumnGrid` (AgGridTable + createToolbar runtime) is exported from the root api via its own module, grid-less consumers can import the columnState barrel directly.
28
+ - Changed (A2): `getLogsApi<T>()` builds fresh per-call state (own map/mini/settings; optional `settingsKey` in `LogsApiOptions` persists instance settings); the global `logsApi` explicitly injects the legacy shared module state, so all existing consumers and QA card 9 are behavior-identical; React views bound per instance via optional state props defaulting to the legacy state.
29
+ - Changed (A5): the quadruplicated `filter(!fixed)`+pinFixed idiom (columnState.normalize, ColumnsMenu.movedOrder, Toolbar.normalize, Toolbar.Settings.movedOrder) now shares `utils/fixedOrder.ts` (`pinFixedOrder`, `movedOrderWithFixed`), exported from the utils barrel; dead `renderBy`/`updateBy` imports dropped from columnState.
30
+ - Changed (A6): `updateBy` - imperative `f` goes through a ref (inline callbacks no longer resubscribe every render); React notifiers and imperative callbacks live in separate sets, so `renderByLast`/`renderByRevers` affect ONLY React subscribers while `f` callbacks always run in subscription order (before React notifiers), matching the documented contract; reentrant `renderBy` on the same object coalesces (bounded passes).
31
+ - Changed (A8): `createToolbar().api.dispose()` and `createColumnGrid().dispose()` release factory-lifetime subscriptions (external-source onChange, config onChange, pending fit RAF) - repeated factories over one key no longer accumulate permanent listeners; `createColumnGrid` re-seeds `visibleCount` on grid re-attach so the auto-fit is not mis-skipped after a remount.
32
+ - Changed (A10, safe part): `searchHistory.items`/`use()` no longer mutate persisted state on read (normalization moved to the write path); `window` reads guarded in `RightMenu` render clamp, `LeftModal.useViewport`, and `ApiLeftMenu`'s import-time default element (SSR/tests).
33
+ - Verification (architecture pass): `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (45/45); `npm run build`; QA stand 3010 fresh load with zero console errors; spot checks - card 21 place persisted across reload (persistedMaps path), card 25 toolbar click updates last action (fixedOrder + updateBy path), card 32 table/cards switch + dots overlay, card 9 add-log appends rows (isolated getLogsApi, global path).
34
+ - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (45/45); `npm run build`; QA stand 3010: card 21 place switch survives F5 (hook persistence), card 19 fixed-parent 150->240px updates select 92->180px and container 150x51->240x51 via `useElementSize`, card 9 add-log appends rows through the controller.
35
+ - Verification: `npx tsc --noEmit`; `npm run build`; QA stand `http://127.0.0.1:3010/` and `/src/testReact.tsx` return HTTP 200.
36
36
  - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand`; `npm run build`; `npm pack .\dist --json` confirmed `doc/changes/v1.0.41.md`, `doc/wenay-react2.md`, `doc/wenay-react2-rare.md`, `doc/EXAMPLE_USAGE.md`, and `doc/target/my.md`.
@@ -1,26 +1,26 @@
1
- # v1.0.42
2
-
3
- Date: 2026-07-10
4
-
5
- Status: released.
6
-
7
- - Added: `onClickClose` prop on `FloatingWindow`/`FloatingWindowBase`; the historical `onCLickClose` typo prop stays as a deprecated compatibility alias (`onClickClose` wins when both are set). Internal `SettingsDialog` usage migrated. (A10)
8
- - Added: `keyForSave` prop on `useOutside`'s `Button` (same persist-key naming as `FloatingWindow`/`Resizable`/`RightMenu`); the old `keySave` stays as a deprecated compatibility alias. Factory-option `key` names (`createToolbar`, `staticGetAdd`, `createUiSlot`) are not React props and stay as is. (A10)
9
- - Fixed: duplicate QA card numbering - Replay per-key feed and route hand-off cards renumbered 25→33 and 26→34; card 25 (createToolbar) and 26 (useReorder) keep their numbers. (A10)
10
- - Added: `__test/tokens.test.ts` - permanent jest guarantee that `tokens.ts` mirrors `tokens.css` in both directions (CSS `var()` references resolved before compare). Chosen instead of codegen so the build pipeline stays untouched. (A10)
11
- - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (47/47).
12
- - Added: `structEqual` in `utils/structEqual.ts` (exported from the utils barrel) - structural deep equality with the `JSON.stringify` idiom's tolerances (undefined props absent, NaN==NaN) but without its key-order sensitivity. (A10)
13
- - Changed: `columnState.readFromGrid` change/loop guards use `structEqual` instead of `JSON.stringify` - the grid's `getFilterModel()` key order no longer counts as a change (false-positive commits gone). The order-SENSITIVE `sameMap` presence guard is intentionally untouched: its order sensitivity re-imposes the stored order when columnDefs reset the grid's order. (A10)
14
- - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (53/53, incl. new `structEqual` suite).
15
- - Added (A7): `useDraggableApi` options `onMove` (imperative per-move-tick callback through a ref; not fired on setPosition/reset/cancel or release) and `trackState: false` (position lives only in `positionRef`/`onMove`, no re-render per move tick; start/end re-renders remain). Both additive, defaults unchanged.
16
- - Changed (A7): `DragBox` is now a thin adapter over `useDraggableApi` (holdMs 0, trackState false, onMove) instead of its own document-listener loop. Contract preserved and pinned by the new permanent `__test/dragBox.test.tsx`: immediate start, per-tick imperative `onX`/`onY` with the delta from the press point, mouse `preventDefault` / touch none, touch identifier tracking, no re-render per move tick, `onStop` (no args) only after a real gesture, `last` ref shares the live position object, per-gesture delta reset. The unused `dragging` prop stays accepted.
17
- - Changed (A7): `DragArea` marked `@deprecated` (no consumers; unique semantics - `document.body` listeners, `stopPropagation` per move tick, ABSOLUTE coords, no preventDefault - re-basing would change observable behavior). Removal only in a deliberate breaking version.
18
- - Decided (A7): `useFloatingWindowController`'s header loop and `StickerMenu` stay on their own loops - their unique behavior is load-bearing (viewport clamp + `e.buttons===1` release recovery + persist/z-stack; mount-lifetime listeners + click-vs-drag suppression respectively).
19
- - Changed: QA card 35 added - DragBox delta drag with starts/stops/renders counters (renders must NOT grow per move pixel).
20
- - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (58/58); stand 3010 fresh load zero console errors, card 35 two consecutive drags (commit without jump-back, renders 1+2 per gesture), card 26 reorder drag commits once (`B C A D E F G H`, commits 1).
21
- - Added (A9): internal `components/Overlay.tsx` - the one portal+scrim+outside-click+Escape composition for scrim-based modal systems. Deliberately NOT exported from public barrels; outside-click delegates to `OutsideClickArea` (mousedown+touchstart semantics preserved); the Escape listener attaches only when `onEscape` is passed; callbacks go through refs. Also the DOM seam for a future react-native view layer.
22
- - Changed (A9): `ModalProvider` composes `Overlay` (inline token scrim + flex centering, `tokens.zIndex.modal`, Escape gated by `closeOnEscape`, outside by `closeOnOutsideClick`) - public API and observable behavior unchanged; pinned by new permanent `__test/modalProvider.test.tsx`.
23
- - Changed (A9): `SettingsDialog` composes `Overlay` (`wenayDlgScrim`/`wenayDlgOutside` classes, window zIndex 10000 over the 9999 scrim); NO `onEscape` - the controller keeps its two-stage Escape (clear search, then close).
24
- - Decided (A9): `createModalElementStore` (render-slot store, not chrome), `LeftModal`/`Modal2` drawer (gesture-driven, scrim-less), and `ModalWrapper`/Input helpers (backdrop-less by design, hosted inside another modal slot) are NOT overlays and stay separate.
25
- - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (62/62); stand 3010 fresh load zero console errors; card 13 - open, Escape, outside click, close button (scrim dims, token z-index); card 20 - open, two-stage Escape (clear -> close), outside click, window x.
26
- - Changed: docs updated for the whole pass - `wenay-react2.md` (useDraggableApi `onMove`/`trackState`, `onClickClose` in the FloatingWindow example, `Button` `keyForSave`), `wenay-react2-rare.md` (internal Overlay note in Modal Low Level, A7 drag-primitives outcome in Drag/Resize Low Level, `structEqual` in utilities, Cleanup Inventory refreshed: getLogsApi/onCLickClose marked fixed/aliased, DragArea and keySave added as deprecated); session note in `doc/progress/architecture-fix-queue.md`.
1
+ # v1.0.42
2
+
3
+ Date: 2026-07-10
4
+
5
+ Status: released.
6
+
7
+ - Added: `onClickClose` prop on `FloatingWindow`/`FloatingWindowBase`; the historical `onCLickClose` typo prop stays as a deprecated compatibility alias (`onClickClose` wins when both are set). Internal `SettingsDialog` usage migrated. (A10)
8
+ - Added: `keyForSave` prop on `useOutside`'s `Button` (same persist-key naming as `FloatingWindow`/`Resizable`/`RightMenu`); the old `keySave` stays as a deprecated compatibility alias. Factory-option `key` names (`createToolbar`, `staticGetAdd`, `createUiSlot`) are not React props and stay as is. (A10)
9
+ - Fixed: duplicate QA card numbering - Replay per-key feed and route hand-off cards renumbered 25→33 and 26→34; card 25 (createToolbar) and 26 (useReorder) keep their numbers. (A10)
10
+ - Added: `__test/tokens.test.ts` - permanent jest guarantee that `tokens.ts` mirrors `tokens.css` in both directions (CSS `var()` references resolved before compare). Chosen instead of codegen so the build pipeline stays untouched. (A10)
11
+ - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (47/47).
12
+ - Added: `structEqual` in `utils/structEqual.ts` (exported from the utils barrel) - structural deep equality with the `JSON.stringify` idiom's tolerances (undefined props absent, NaN==NaN) but without its key-order sensitivity. (A10)
13
+ - Changed: `columnState.readFromGrid` change/loop guards use `structEqual` instead of `JSON.stringify` - the grid's `getFilterModel()` key order no longer counts as a change (false-positive commits gone). The order-SENSITIVE `sameMap` presence guard is intentionally untouched: its order sensitivity re-imposes the stored order when columnDefs reset the grid's order. (A10)
14
+ - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (53/53, incl. new `structEqual` suite).
15
+ - Added (A7): `useDraggableApi` options `onMove` (imperative per-move-tick callback through a ref; not fired on setPosition/reset/cancel or release) and `trackState: false` (position lives only in `positionRef`/`onMove`, no re-render per move tick; start/end re-renders remain). Both additive, defaults unchanged.
16
+ - Changed (A7): `DragBox` is now a thin adapter over `useDraggableApi` (holdMs 0, trackState false, onMove) instead of its own document-listener loop. Contract preserved and pinned by the new permanent `__test/dragBox.test.tsx`: immediate start, per-tick imperative `onX`/`onY` with the delta from the press point, mouse `preventDefault` / touch none, touch identifier tracking, no re-render per move tick, `onStop` (no args) only after a real gesture, `last` ref shares the live position object, per-gesture delta reset. The unused `dragging` prop stays accepted.
17
+ - Changed (A7): `DragArea` marked `@deprecated` (no consumers; unique semantics - `document.body` listeners, `stopPropagation` per move tick, ABSOLUTE coords, no preventDefault - re-basing would change observable behavior). Removal only in a deliberate breaking version.
18
+ - Decided (A7): `useFloatingWindowController`'s header loop and `StickerMenu` stay on their own loops - their unique behavior is load-bearing (viewport clamp + `e.buttons===1` release recovery + persist/z-stack; mount-lifetime listeners + click-vs-drag suppression respectively).
19
+ - Changed: QA card 35 added - DragBox delta drag with starts/stops/renders counters (renders must NOT grow per move pixel).
20
+ - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (58/58); stand 3010 fresh load zero console errors, card 35 two consecutive drags (commit without jump-back, renders 1+2 per gesture), card 26 reorder drag commits once (`B C A D E F G H`, commits 1).
21
+ - Added (A9): internal `components/Overlay.tsx` - the one portal+scrim+outside-click+Escape composition for scrim-based modal systems. Deliberately NOT exported from public barrels; outside-click delegates to `OutsideClickArea` (mousedown+touchstart semantics preserved); the Escape listener attaches only when `onEscape` is passed; callbacks go through refs. Also the DOM seam for a future react-native view layer.
22
+ - Changed (A9): `ModalProvider` composes `Overlay` (inline token scrim + flex centering, `tokens.zIndex.modal`, Escape gated by `closeOnEscape`, outside by `closeOnOutsideClick`) - public API and observable behavior unchanged; pinned by new permanent `__test/modalProvider.test.tsx`.
23
+ - Changed (A9): `SettingsDialog` composes `Overlay` (`wenayDlgScrim`/`wenayDlgOutside` classes, window zIndex 10000 over the 9999 scrim); NO `onEscape` - the controller keeps its two-stage Escape (clear search, then close).
24
+ - Decided (A9): `createModalElementStore` (render-slot store, not chrome), `LeftModal`/`Modal2` drawer (gesture-driven, scrim-less), and `ModalWrapper`/Input helpers (backdrop-less by design, hosted inside another modal slot) are NOT overlays and stay separate.
25
+ - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (62/62); stand 3010 fresh load zero console errors; card 13 - open, Escape, outside click, close button (scrim dims, token z-index); card 20 - open, two-stage Escape (clear -> close), outside click, window x.
26
+ - Changed: docs updated for the whole pass - `wenay-react2.md` (useDraggableApi `onMove`/`trackState`, `onClickClose` in the FloatingWindow example, `Button` `keyForSave`), `wenay-react2-rare.md` (internal Overlay note in Modal Low Level, A7 drag-primitives outcome in Drag/Resize Low Level, `structEqual` in utilities, Cleanup Inventory refreshed: getLogsApi/onCLickClose marked fixed/aliased, DragArea and keySave added as deprecated); session note in `doc/progress/architecture-fix-queue.md`.
@@ -1,10 +1,10 @@
1
- # v1.0.43
2
-
3
- Date: 2026-07-10
4
-
5
- Status: released.
6
-
7
- - Changed: `ColumnDots` drag is LIVE (user request from the stand, card 32: "отображать и выключать надо по мере движения — я так буду искать нужный мне столбик"): every EMPTY mark the dot crosses swaps the shown column immediately - the grid/cards follow the finger during the drag; the drop no longer commits a swap (it only settles selection and the tear-off). Occupied marks are passed over; a fixed dot still never swaps; the vertical tear-off flick hides the CURRENTLY shown column of the gesture.
8
- - Added: a small label above the dragged dot (`.wenayColDotsDragLabel`, tokens `--cols-dots-drag-label-bg/color/font-size` + TS mirror) naming the currently shown column - sized for a finger on a phone.
9
- - Changed: QA cards 29 and 32 do/expect texts updated to the live semantics.
10
- - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (62/62, incl. the two-way tokens sync test); stand 3010 card 32 - dragging the NOTE dot left across QTY to PRICE leaves the table showing NAME+PRICE with the dot settled on the PRICE mark (the drop path commits nothing, so the end state proves the live path), zero console errors.
1
+ # v1.0.43
2
+
3
+ Date: 2026-07-10
4
+
5
+ Status: released.
6
+
7
+ - Changed: `ColumnDots` drag is LIVE (user request from the stand, card 32: "отображать и выключать надо по мере движения — я так буду искать нужный мне столбик"): every EMPTY mark the dot crosses swaps the shown column immediately - the grid/cards follow the finger during the drag; the drop no longer commits a swap (it only settles selection and the tear-off). Occupied marks are passed over; a fixed dot still never swaps; the vertical tear-off flick hides the CURRENTLY shown column of the gesture.
8
+ - Added: a small label above the dragged dot (`.wenayColDotsDragLabel`, tokens `--cols-dots-drag-label-bg/color/font-size` + TS mirror) naming the currently shown column - sized for a finger on a phone.
9
+ - Changed: QA cards 29 and 32 do/expect texts updated to the live semantics.
10
+ - Verification: `npx tsc -p tsconfig.qa-check.json --noEmit`; `npm run testjest -- --runInBand` (62/62, incl. the two-way tokens sync test); stand 3010 card 32 - dragging the NOTE dot left across QTY to PRICE leaves the table showing NAME+PRICE with the dot settled on the PRICE mark (the drop path commits nothing, so the end state proves the live path), zero console errors.
@@ -1,14 +1,14 @@
1
- # v1.0.44
2
-
3
- Date: 2026-07-10
4
-
5
- Status: released.
6
-
7
- - Fixed: `createColumnState` no longer persists `visible=false` for a column
8
- hidden solely by runtime `presentGate` when another grid interaction is read
9
- back. Opening the gate restores the user's saved visibility as intended.
10
- - Added: regression coverage for gated-column readback and normal user
11
- visibility changes while the gate is open.
12
- - Verification: repro first failed on v1.0.43 (`visible.b` became `false` after
13
- an unrelated resize); targeted Jest 3/3; full Jest 64/64; `tsc -p
14
- tsconfig.qa-check.json --noEmit`; `pnpm run build`.
1
+ # v1.0.44
2
+
3
+ Date: 2026-07-10
4
+
5
+ Status: released.
6
+
7
+ - Fixed: `createColumnState` no longer persists `visible=false` for a column
8
+ hidden solely by runtime `presentGate` when another grid interaction is read
9
+ back. Opening the gate restores the user's saved visibility as intended.
10
+ - Added: regression coverage for gated-column readback and normal user
11
+ visibility changes while the gate is open.
12
+ - Verification: repro first failed on v1.0.43 (`visible.b` became `false` after
13
+ an unrelated resize); targeted Jest 3/3; full Jest 64/64; `tsc -p
14
+ tsconfig.qa-check.json --noEmit`; `pnpm run build`.
@@ -1,9 +1,9 @@
1
- # v1.0.45 — Media and Peer React adapters
2
-
3
- Status: ready for publication.
4
-
5
- - Updated `wenay-common2` to `^1.0.73`.
6
- - Added `useMediaSource` for capture lifecycle only; video/audio frames stay outside React state.
7
- - Added `usePeer` as a thin Peer SDK adapter with mirrored store, route/status and explicit resync controls.
8
- - Added QA stand cards 38–40 and focused hook tests.
1
+ # v1.0.45 — Media and Peer React adapters
2
+
3
+ Status: ready for publication.
4
+
5
+ - Updated `wenay-common2` to `^1.0.73`.
6
+ - Added `useMediaSource` for capture lifecycle only; video/audio frames stay outside React state.
7
+ - Added `usePeer` as a thin Peer SDK adapter with mirrored store, route/status and explicit resync controls.
8
+ - Added QA stand cards 38–40 and focused hook tests.
9
9
  - Verified with qa-check TypeScript, full Jest, package build, diff-check and the live stand; manual stand verification was confirmed by the user.
@@ -1,16 +1,16 @@
1
- # v1.0.46 — common2 Calls, presence and media relay adoption
2
-
3
- Status: ready for publication.
4
-
5
- - Updated the declared `wenay-common2` dependency to `^1.0.74`.
6
- - Added `usePeerCalls` over the common2 call manager; React renders call lifecycle but does not own signaling, busy/glare, timeout or closure policy.
7
- - Added `usePeerPresence` for the host presence snapshot and edge stream.
8
- - Added QA cards 41–44 and in-process tests for call lifecycle, presence edges and ACL revocation; card 43 carries a real camera frame through Media → relay → policy-filtered canvas.
1
+ # v1.0.46 — common2 Calls, presence and media relay adoption
2
+
3
+ Status: ready for publication.
4
+
5
+ - Updated the declared `wenay-common2` dependency to `^1.0.74`.
6
+ - Added `usePeerCalls` over the common2 call manager; React renders call lifecycle but does not own signaling, busy/glare, timeout or closure policy.
7
+ - Added `usePeerPresence` for the host presence snapshot and edge stream.
8
+ - Added QA cards 41–44 and in-process tests for call lifecycle, presence edges and ACL revocation; card 43 carries a real camera frame through Media → relay → policy-filtered canvas.
9
9
  - Verified with qa-check TypeScript, Jest, package build and the QA stand.
10
10
  - Ships doc/examples/peer-call-media.tsx: a consumer template for calls, presence, camera/microphone relay and server-owned ACL.
11
11
 
12
12
  - Ships the entire QA stand as wenay-react2/demo/stand; Active cards are canonical examples, Archive cards are regression/compat context.
13
-
14
- - Added runtime-only live drag-preview across `useReorder`, Toolbar, ColumnsMenu, columnState and attached grid; persistence still happens only on drop.
15
- - Added optional `CardList layout="compact"` for dense mobile key/value cards; the stacked layout remains default.
13
+
14
+ - Added runtime-only live drag-preview across `useReorder`, Toolbar, ColumnsMenu, columnState and attached grid; persistence still happens only on drop.
15
+ - Added optional `CardList layout="compact"` for dense mobile key/value cards; the stacked layout remains default.
16
16
  - Rebuilt QA card 27 controls on one CSS-grid track system so remove/insert actions cannot drift from their columns.
@@ -1,9 +1,9 @@
1
- # 1.0.47
2
-
3
- - Added the platform-neutral `wenay-react2/native` entrypoint.
4
- - Added `createNativeColumnState`: an AsyncStorage-compatible controller for order, visibility, width, sorting, filters and groups without DOM, CSS, React DOM or ag-grid dependencies.
5
- - Added `createNativeColumnDots`: a renderer-independent gesture model with live replacement, reorder, visibility, sorting and tear-off behavior.
6
- - Added focused hydration, persistence, gesture and dependency-boundary tests.
7
- - Fixed the shipped examples list formatting in README.
8
-
9
- Verification: Jest, TypeScript build, package archive inspection and runtime import of `wenay-react2/native` from the packed artifact.
1
+ # 1.0.47
2
+
3
+ - Added the platform-neutral `wenay-react2/native` entrypoint.
4
+ - Added `createNativeColumnState`: an AsyncStorage-compatible controller for order, visibility, width, sorting, filters and groups without DOM, CSS, React DOM or ag-grid dependencies.
5
+ - Added `createNativeColumnDots`: a renderer-independent gesture model with live replacement, reorder, visibility, sorting and tear-off behavior.
6
+ - Added focused hydration, persistence, gesture and dependency-boundary tests.
7
+ - Fixed the shipped examples list formatting in README.
8
+
9
+ Verification: Jest, TypeScript build, package archive inspection and runtime import of `wenay-react2/native` from the packed artifact.
@@ -0,0 +1,8 @@
1
+ # 1.0.48
2
+
3
+ - Fixed swapped display labels of the logs settings: `minVarLogs` is the log-table importance filter, `minVarMessage` gates notifications (keys and behavior unchanged).
4
+ - Peer call demo (`demo/peer-media`, QA card 45): the audio enable intent is remembered and applied to the player created after accept, so the documented order (enable -> call -> accept) produces sound without a second click.
5
+ - QA stand: stale card texts corrected (14 reset label, 29 unreachable "Max 4 dots", 38 phantom "requesting" state, card 2 note); card 2 switched off the deprecated `onCLickClose` typo alias to `onClickClose`.
6
+ - `typescript ^5` is pinned in devDependencies (the build used to rely on a global install; TS 7 is incompatible with ts-jest and `moduleResolution: node10`).
7
+
8
+ Verification: tsc qa-check, Jest 23/78 green, library build, and a full automated walkthrough of all 43 QA stand cards with zero console errors (`doc/progress/qa-stand-walkthrough-2026-07-12.md`; manual-device checks listed there).
@@ -0,0 +1,7 @@
1
+ # 1.0.49
2
+
3
+ - Added the conference demo `wenay-react2/demo/peer-conference` (QA card 46): a 3-way host-star room exercising BOTH media technologies — `Peer.createMediaRelay` fan-out gated by call-derived room membership, and a policy-routed focus pair over `Replay.createRouteCoordinator` whose relay hop (`serveReplayChannel` over an in-proc text channel) and WebRTC direct route (`createWebRtcConnector`/`acceptWebRtcDirect`) serve ONE owner-sequenced line, making every hand-off gap-free by seq. Includes a headless `createConferenceWorld` factory (injectable rtc/fps/frame for tests) and a fake-RTC loopback runtime ported from the common2 oracle.
4
+ - Added `useRouteState(coordinator, link)`: thin React binding for route chips — live route state, last denial/failure reason, 500ms connector metrics and a short hand-off log.
5
+ - Added the real-backend conference example: `doc/examples/conference-server.mjs` (Node + Socket.IO peer host with a server-owned room policy: accepted calls join both parties, WebRTC offers are brokered only inside a shared room, `canWatch` reads shared-room membership) and `doc/examples/conference-client.html/.ts` (one seat per tab: media grid through the server relay, peer-store rows promotable to a real RTCPeerConnection datachannel and back).
6
+
7
+ Verification: tsc qa-check; Jest 25 suites / 87 tests including 9 new (star-room roster, relay ACL follows membership, relay-hop seq authority, gap-free promote/demote over loopback RTC, loud policy denials with recovery, server revoke + transport-death fallbacks, late-joiner catch-up, React flow, useRouteState transitions); library build; stand card 46 walkthrough.
@@ -1,6 +1,7 @@
1
- # Runnable consumer examples
2
-
3
- - [`peer-call-media.tsx`](peer-call-media.tsx) — re-exports the interactive Peer/Media demos used by QA cards 41–44. Install the package and import from `wenay-react2/demo/peer-media`.
4
-
1
+ # Runnable consumer examples
2
+
3
+ - [`peer-call-media.tsx`](peer-call-media.tsx) — re-exports the interactive Peer/Media demos used by QA cards 41–44. Install the package and import from `wenay-react2/demo/peer-media`.
4
+
5
5
  The component uses an in-process `Peer.createPeerHost`, so calls, presence, camera and microphone relay work without server credentials. For production, expose `publishOf(account)` and `watchOf(account)` from the RPC server and keep `canWatch` plus call authorization on that server.
6
6
  - [stand.tsx](stand.tsx) — exports the entire interactive stand from wenay-react2/demo/stand. Active cards teach canonical APIs; Archive cards remain visible regression/compat examples.
7
+ - [`conference-server.mjs`](conference-server.mjs) + [`conference-client.html`](conference-client.html) / [`conference-client.ts`](conference-client.ts) — the real-backend conference bridge (QA card 46's in-process world is `wenay-react2/demo/peer-conference`). Run `node doc/examples/conference-server.mjs` (needs `socket.io`), serve the client page from any dev server that transpiles TS (the wenay-react2 stand does), and open 2-3 tabs with `?me=a&peers=b,c&host=1` / `?me=b&peers=a,c` / `?me=c&peers=a,b`. The server owns the room policy (accepted calls join both parties to the room; WebRTC offers are brokered only inside a shared room) and the media relay; each peer row in a tab can promote its store link to a real RTCPeerConnection datachannel and re-interpose back.
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>wenay conference seat</title>
7
+ <style>
8
+ body { font-family: system-ui, sans-serif; margin: 16px; background: #f6f8fa; color: #1f2328; }
9
+ h2 { margin: 0 0 4px; }
10
+ .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 8px 0; }
11
+ .tiles { display: flex; gap: 12px; flex-wrap: wrap; }
12
+ figure { margin: 0; display: grid; gap: 2px; }
13
+ img.tile { width: 160px; height: 90px; background: #111; border-radius: 6px; object-fit: cover; }
14
+ figcaption, .fine { font-size: 12px; color: #57606a; }
15
+ .peers { display: grid; gap: 6px; margin: 8px 0; }
16
+ .peerRow { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; background: #fff; border: 1px solid #d0d7de; border-radius: 8px; padding: 6px 10px; }
17
+ .chip { padding: 1px 8px; border-radius: 10px; font-size: 12px; color: #fff; background: #0969da; }
18
+ .chip.direct { background: #1a7f37; }
19
+ .chip.fallback, .chip.offline { background: #cf222e; }
20
+ #log { font: 11px/1.5 monospace; background: #fff; border: 1px solid #d0d7de; border-radius: 8px; padding: 8px; max-height: 180px; overflow: auto; }
21
+ button { cursor: pointer; }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <h2 id="who">conference seat</h2>
26
+ <div class="fine">media grid = server relay (room ACL) · peer rows = store links, promotable to a real WebRTC datachannel</div>
27
+ <div class="row" id="callControls"></div>
28
+ <div class="tiles" id="tiles"></div>
29
+ <div class="peers" id="peers"></div>
30
+ <div class="row"><input id="status" placeholder="my status (syncs over the peer store)" style="flex: 1; max-width: 320px;" /></div>
31
+ <div id="log"></div>
32
+ <script type="module" src="./conference-client.ts"></script>
33
+ </body>
34
+ </html>
@@ -0,0 +1,212 @@
1
+ // Browser seat for conference-server.mjs — the real-backend variant of demo/peer-conference.
2
+ // One tab = one participant. Media tiles ride the SERVER relay (room-gated fan-out);
3
+ // each peer row is a peer-store link that starts on the server relay and can promote to
4
+ // a real RTCPeerConnection datachannel (negotiated through the same server signal hub),
5
+ // then re-interpose back — the two technologies of the conference bridge, cross-tab.
6
+ //
7
+ // Serve this page from the wenay-react2 dev stand (vite transpiles the .ts):
8
+ // /doc/examples/conference-client.html?me=a&peers=b,c&host=1&room=demo
9
+ import {io} from "socket.io-client";
10
+ import {createRpcClientHub, Peer} from "wenay-common2";
11
+
12
+ type SeatWorld = {name: string, hue: number, beat: number, status: string};
13
+ type ConfFrame = {n: number, at: number, image: string};
14
+
15
+ const params = new URLSearchParams(location.search);
16
+ const me = params.get("me") ?? "a";
17
+ const peers = (params.get("peers") ?? (me === "a" ? "b,c" : "a")).split(",").map(value => value.trim()).filter(Boolean);
18
+ const isHost = params.get("host") === "1" || me === "a";
19
+ const room = params.get("room") ?? "demo";
20
+ const serverUrl = params.get("server") ?? `${location.protocol}//${location.hostname}:8391`;
21
+
22
+ const el = (id: string) => document.getElementById(id)!;
23
+ const logBox = el("log");
24
+ function log(line: string) {
25
+ const row = document.createElement("div");
26
+ row.textContent = `${new Date().toLocaleTimeString()} ${line}`;
27
+ logBox.prepend(row);
28
+ while (logBox.children.length > 60) logBox.lastChild?.remove();
29
+ }
30
+
31
+ const seatHue = (account: string) => {
32
+ let hash = 0;
33
+ for (const ch of account) hash = (hash * 31 + ch.charCodeAt(0)) % 360;
34
+ return hash;
35
+ };
36
+ const paintFrame = (account: string, n: number): ConfFrame => {
37
+ const hue = seatHue(account);
38
+ const cx = 16 + (n * 9) % 128;
39
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="160" height="90">` +
40
+ `<rect width="160" height="90" fill="hsl(${hue},65%,42%)"/>` +
41
+ `<circle cx="${cx}" cy="${24 + (n * 5) % 44}" r="7" fill="#fff" opacity="0.9"/>` +
42
+ `<text x="8" y="82" font-size="12" font-family="monospace" fill="#fff">${account} #${n}</text></svg>`;
43
+ return {n, at: Date.now(), image: "data:image/svg+xml," + encodeURIComponent(svg)};
44
+ };
45
+
46
+ async function main() {
47
+ document.title = `conf seat ${me}`;
48
+ el("who").textContent = `seat ${me}${isHost ? " (host)" : ""} · room "${room}" · peers: ${peers.join(", ")}`;
49
+
50
+ const hub = createRpcClientHub(
51
+ () => io(serverUrl, {transports: ["websocket"], auth: {account: me}}),
52
+ r => ({app: r<any>("app")}) as const,
53
+ );
54
+ const clients = await hub.setToken(null);
55
+ await clients.app.readyStrict();
56
+ const deep: any = clients.app.func;
57
+ log("connected; server time " + await deep.serverTime());
58
+
59
+ // ============== calls: host-star membership over the server policy ==============
60
+ const manager = Peer.createCallManager({port: Peer.callPortOf(deep.peer), self: me});
61
+ const live = new Map<string, any>();
62
+ const controls = el("callControls");
63
+ function renderCalls() {
64
+ controls.innerHTML = "";
65
+ const inRoom = [...live.values()].some(call => call.state() === "active");
66
+ const state = document.createElement("b");
67
+ state.textContent = inRoom ? "in room" : "not in room";
68
+ controls.append(state);
69
+ if (isHost) {
70
+ for (const peer of peers) {
71
+ const current = [...live.values()].find(call => call.peer === peer && call.state() !== "ended");
72
+ const button = document.createElement("button");
73
+ if (!current) {
74
+ button.textContent = `ring ${peer}`;
75
+ button.onclick = () => bindCall(manager.call(peer, {room}));
76
+ } else if (current.state() === "ringing") {
77
+ button.textContent = `cancel ${peer}…`;
78
+ button.onclick = () => current.hangup();
79
+ } else {
80
+ button.textContent = `hang up ${peer}`;
81
+ button.onclick = () => current.hangup();
82
+ }
83
+ controls.append(button);
84
+ }
85
+ } else {
86
+ const incoming = [...live.values()].find(call => call.direction === "in" && call.state() === "ringing");
87
+ if (incoming) {
88
+ const accept = document.createElement("button");
89
+ accept.textContent = `accept ${incoming.peer}`;
90
+ accept.onclick = () => incoming.accept();
91
+ const decline = document.createElement("button");
92
+ decline.textContent = "decline";
93
+ decline.onclick = () => incoming.decline();
94
+ controls.append(accept, decline);
95
+ }
96
+ const active = [...live.values()].find(call => call.state() === "active");
97
+ if (active) {
98
+ const leave = document.createElement("button");
99
+ leave.textContent = "leave room";
100
+ leave.onclick = () => active.hangup();
101
+ controls.append(leave);
102
+ }
103
+ }
104
+ }
105
+ function bindCall(call: any) {
106
+ live.set(call.id, call);
107
+ call.changed.on((state: string) => { log(`call ${call.peer}: ${state}${call.reason() ? ` (${call.reason()})` : ""}`); renderCalls(); });
108
+ void call.ended.then(() => { live.delete(call.id); renderCalls(); });
109
+ renderCalls();
110
+ }
111
+ manager.rings.on(bindCall);
112
+ await manager.ready;
113
+ renderCalls();
114
+ const amInRoom = () => [...live.values()].some(call => call.state() === "active");
115
+
116
+ // ============== technology 1: media grid through the server relay ==============
117
+ let frameN = 0;
118
+ setInterval(function publishSyntheticCam() {
119
+ if (!amInRoom()) return; // stream only while in the room (the server ACL gates viewers anyway)
120
+ frameN++;
121
+ const frame = paintFrame(me, frameN);
122
+ void deep.media.publish("cam", frame, frame.at);
123
+ }, 125);
124
+
125
+ const tiles = el("tiles");
126
+ for (const peer of peers) {
127
+ const figure = document.createElement("figure");
128
+ const img = document.createElement("img");
129
+ img.className = "tile";
130
+ const caption = document.createElement("figcaption");
131
+ caption.textContent = `${peer} · waiting`;
132
+ figure.append(img, caption);
133
+ tiles.append(figure);
134
+ // the server's watch proxy resolves per access through canWatch: a subscription
135
+ // made before the room grant lands on nothing, so keep re-attaching until the
136
+ // FIRST FRAME proves the line is live (then stop churning)
137
+ let off: (() => void) | null = null;
138
+ let lastN = 0, lastAt = 0;
139
+ const attach = () => {
140
+ if (lastAt) return;
141
+ try { off?.(); } catch { /* stale RPC handle */ }
142
+ off = null;
143
+ try {
144
+ const line = deep.media.watch?.[peer]?.cam;
145
+ if (!line?.on) return;
146
+ off = line.on((frame: ConfFrame) => {
147
+ if (!lastAt) log(`watching ${peer} through the server relay`);
148
+ lastN = frame.n; lastAt = frame.at;
149
+ img.src = frame.image;
150
+ });
151
+ } catch (error) { log(`watch ${peer} failed: ${error}`); }
152
+ };
153
+ setInterval(attach, 1500);
154
+ setInterval(() => {
155
+ const age = lastAt ? Math.round((Date.now() - lastAt) / 100) / 10 : 0;
156
+ caption.textContent = `${peer} · #${lastN}${lastAt ? ` · ${age}s ago` : " · waiting"}`;
157
+ }, 500);
158
+ attach();
159
+ }
160
+
161
+ // ============== technology 2: peer-store links, relay <-> direct per pair ==============
162
+ const client = Peer.createPeerClient<SeatWorld>({
163
+ remote: deep.peer,
164
+ account: me,
165
+ initial: {name: me, hue: seatHue(me), beat: 0, status: ""},
166
+ rtc: () => new RTCPeerConnection(),
167
+ drain: "micro",
168
+ });
169
+ client.onRoute((event: any) => log(`route ${event.key}: ${event.from} -> ${event.to}${event.reason ? ` (${String(event.reason)})` : ""}`));
170
+ setInterval(function heartbeat() { client.store.state.beat++; }, 1000);
171
+ (el("status") as HTMLInputElement).addEventListener("input", function onStatusInput(event) {
172
+ client.store.state.status = (event.target as HTMLInputElement).value;
173
+ });
174
+
175
+ const peersBox = el("peers");
176
+ for (const account of peers) {
177
+ const link = client.peer(account);
178
+ const row = document.createElement("div");
179
+ row.className = "peerRow";
180
+ const title = document.createElement("b");
181
+ const chip = document.createElement("span");
182
+ const info = document.createElement("span");
183
+ info.className = "fine";
184
+ const direct = document.createElement("button");
185
+ direct.textContent = "go direct";
186
+ direct.onclick = async () => {
187
+ log(`promoteDirect ${account}…`);
188
+ const result = await link.promoteDirect({timeoutMs: 8000});
189
+ log(result.ok ? `direct with ${account}: ok (${result.state})` : `direct with ${account} denied: ${String(result.reason)}`);
190
+ };
191
+ const relay = document.createElement("button");
192
+ relay.textContent = "back to relay";
193
+ relay.onclick = async () => {
194
+ const result = await link.reinterposeRelay("manual");
195
+ log(result.ok ? `relay with ${account}` : `re-interpose ${account} failed: ${String(result.reason)}`);
196
+ };
197
+ row.append(title, chip, info, direct, relay);
198
+ peersBox.append(row);
199
+ setInterval(function renderPeerRow() {
200
+ const mirrored: any = link.store.state;
201
+ title.textContent = account;
202
+ const route = link.route();
203
+ chip.textContent = `${route} · ${link.state()}`;
204
+ chip.className = "chip" + (route === "direct" ? " direct" : link.state() === "fallback" ? " fallback" : "");
205
+ info.textContent = mirrored
206
+ ? `beat ${mirrored.beat} · seq ${link.seq()}${mirrored.status ? ` · "${mirrored.status}"` : ""}`
207
+ : "no mirror yet";
208
+ }, 500);
209
+ }
210
+ }
211
+
212
+ main().catch(error => { console.error(error); log("FATAL: " + error); });