fsanyoto commited on
Commit
bf31e36
Β·
verified Β·
1 Parent(s): 15e3e59

Deploy AIOS web (React glide grid + FastAPI slice)

Browse files
RELEASES.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "current": "85374f1",
3
  "releases": [
4
  {
5
  "version": "v29",
 
1
  {
2
+ "current": "624b9af",
3
  "releases": [
4
  {
5
  "version": "v29",
VERSION CHANGED
@@ -1 +1 @@
1
- 85374f1
 
1
+ 624b9af
web/src/customer-grid/MapView.tsx CHANGED
@@ -384,6 +384,9 @@ export function MapView({
384
  /** A finished drag must not also read as a click on the pin underneath β€” the
385
  * kanban card's lesson (viewModes.tsx), same fix. */
386
  const movedRef = useRef(false);
 
 
 
387
  /** Once the user has zoomed or panned, a data change must NOT yank the view
388
  * back. Before that, refitting on new data is the helpful behaviour. */
389
  const touchedRef = useRef(false);
@@ -1035,7 +1038,24 @@ export function MapView({
1035
  if (e.button !== 0 || !view) return;
1036
  const { x, y } = localPoint(e.clientX, e.clientY);
1037
  movedRef.current = false;
1038
- e.currentTarget.setPointerCapture(e.pointerId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  // Shift (or Ctrl/Cmd) turns the drag into a SELECTION rectangle; a plain
1040
  // drag pans. Both gestures are on the same button because a map that
1041
  // needs a mode toggle to select is a map people never select on.
@@ -1048,6 +1068,21 @@ export function MapView({
1048
 
1049
  const onPointerMove = useCallback(
1050
  (e: ReactPointerEvent<SVGSVGElement>) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1051
  const { x, y } = localPoint(e.clientX, e.clientY);
1052
  if (drag) {
1053
  // ⚠ W37-T30: this used to set `movedRef` UNCONDITIONALLY, and that is what swallowed a
 
384
  /** A finished drag must not also read as a click on the pin underneath β€” the
385
  * kanban card's lesson (viewModes.tsx), same fix. */
386
  const movedRef = useRef(false);
387
+ /** Where a gesture began, in CLIENT pixels, so `onPointerMove` can take the pointer capture
388
+ * only once it has become a drag. See the block in `onPointerDown` (W38-T09). */
389
+ const downRef = useRef<{ id: number; cx: number; cy: number } | null>(null);
390
  /** Once the user has zoomed or panned, a data change must NOT yank the view
391
  * back. Before that, refitting on new data is the helpful behaviour. */
392
  const touchedRef = useRef(false);
 
1038
  if (e.button !== 0 || !view) return;
1039
  const { x, y } = localPoint(e.clientX, e.clientY);
1040
  movedRef.current = false;
1041
+ // β›”β›” THE CAPTURE IS TAKEN LAZILY, IN `onPointerMove`, AND MOVING IT THERE IS THE WHOLE
1042
+ // FIX FOR W38-T09 (found by wave-38 QA on the deployed build). This line used to read
1043
+ // `e.currentTarget.setPointerCapture(e.pointerId)`, unconditionally, on every pointerdown
1044
+ // anywhere in the SVG -- including on a pin. Pointer capture retargets the REST of the
1045
+ // gesture to the capture element, so `pointerup`, `mouseup` and the synthesised `click`
1046
+ // all landed on the `<svg>` and a pin's own `onClick` NEVER RAN. Measured live:
1047
+ // pointerdown -> cg-map-pin mousedown -> cg-map-pin
1048
+ // pointerup -> cg-map-svg mouseup -> cg-map-svg click -> cg-map-svg
1049
+ // ⚠ RELEASING ON `pointerup` DOES NOT SAVE IT, and that was the tempting wrong fix: the
1050
+ // release below already ran and the click was still retargeted. Reproduced in a 20-line
1051
+ // isolation page with zero app code, IDENTICALLY in headed and headless Chromium, so it is
1052
+ // browser behaviour rather than a test-harness artifact.
1053
+ // ⭐ THE PIN'S HANDLER WAS ALWAYS CORRECT. `onOpen`, `pinSelect`, the drawer and the cohort
1054
+ // path were all provably healthy (keyboard Enter opened the record; shift-drag, which the
1055
+ // SVG handles itself and so is immune to the retargeting, selected 2,381 pins). Only
1056
+ // pointer DELIVERY to a pin was broken -- and it had been since wave 8, thirty waves before
1057
+ // the ticket that wired this handler. `git log -S "setPointerCapture"` returns one commit.
1058
+ downRef.current = { id: e.pointerId, cx: e.clientX, cy: e.clientY };
1059
  // Shift (or Ctrl/Cmd) turns the drag into a SELECTION rectangle; a plain
1060
  // drag pans. Both gestures are on the same button because a map that
1061
  // needs a mode toggle to select is a map people never select on.
 
1068
 
1069
  const onPointerMove = useCallback(
1070
  (e: ReactPointerEvent<SVGSVGElement>) => {
1071
+ // ⭐⭐ THE LAZY CAPTURE (W38-T09's fix). A gesture only becomes a drag once it has TRAVELLED,
1072
+ // so the capture is taken here rather than on pointerdown. A zero-movement click therefore
1073
+ // never captures and reaches the pin it was aimed at; a pan or a marquee captures the
1074
+ // instant it stops being a click, which is what keeps the pointer inside this handler when
1075
+ // the cursor leaves the SVG mid-drag. Both directions proven in isolation before this
1076
+ // landed: a plain click fires the pin's own `onClick`, and a real drag still logs the
1077
+ // capture and retargets exactly as it did before.
1078
+ // ⚠ THE THRESHOLD IS IN CLIENT PIXELS, deliberately unlike the two below it. Those compare
1079
+ // LOCAL (viewBox) coordinates to decide whether a gesture counts as travel for selection;
1080
+ // this one asks whether the BROWSER should stop delivering events to what is under the
1081
+ // cursor, which is a screen-space question at every zoom.
1082
+ const dn = downRef.current;
1083
+ if (dn && dn.id === e.pointerId && !e.currentTarget.hasPointerCapture(e.pointerId)
1084
+ && Math.abs(e.clientX - dn.cx) + Math.abs(e.clientY - dn.cy) > 3)
1085
+ e.currentTarget.setPointerCapture(e.pointerId);
1086
  const { x, y } = localPoint(e.clientX, e.clientY);
1087
  if (drag) {
1088
  // ⚠ W37-T30: this used to set `movedRef` UNCONDITIONALLY, and that is what swallowed a
web/src/settings/PermsEditor.tsx CHANGED
@@ -84,6 +84,7 @@ import {
84
  setAccess,
85
  setFilter,
86
  setHidden,
 
87
  toPutBody,
88
  toggleHidden,
89
  } from "./permsModel";
@@ -733,6 +734,22 @@ export function PermsEditor({
733
  of JSX here; it is `settings/ModulePermsList.tsx` now, and
734
  `manage-agent/ManageAgentPane` mounts the SAME component. Nothing
735
  about the list is duplicated, so nothing about it can drift. */}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  <ModulePermsList
737
  modules={payload.modules}
738
  entries={draft}
@@ -740,6 +757,7 @@ export function PermsEditor({
740
  onFilter={(key, next) => setDraft((d) => setFilter(d, key, next))}
741
  onToggleHidden={(key, fk) => setDraft((d) => toggleHidden(d, key, fk))}
742
  onSetHidden={(key, keys) => setDraft((d) => setHidden(d, key, keys))}
 
743
  userOptions={userOptions}
744
  emptyNote="This deployment declares no modules that can be restricted."
745
  headExtra={(m) =>
 
84
  setAccess,
85
  setFilter,
86
  setHidden,
87
+ setMetrics,
88
  toPutBody,
89
  toggleHidden,
90
  } from "./permsModel";
 
734
  of JSX here; it is `settings/ModulePermsList.tsx` now, and
735
  `manage-agent/ManageAgentPane` mounts the SAME component. Nothing
736
  about the list is duplicated, so nothing about it can drift. */}
737
+ {/* β›”β›” `onMetrics` BELOW IS THE LINE W38-T19 SHIPPED WITHOUT, AND EVERY GATE WAS GREEN
738
+ OVER ITS ABSENCE. The capability was complete on both sides β€” `perm_scope.may_metrics`,
739
+ four gated read doors, `_clean_perms`'s fourth entry field, `parseEntry`/`toPutBody`,
740
+ and the checkbox itself in `ModulePermsList` β€” with 39 new gate checks across
741
+ `api_scopes`, `web_login` and `web_ui`. What did not exist was this prop, so the
742
+ component's own `{on && !schemaless && onMetrics}` render gate never fired and the
743
+ toggle the `done-when` names had NO MOUNT SITE anywhere in the product. Found by
744
+ wave-38 QA on the deployed build, by opening the screen and looking.
745
+ ⭐ THE PROP IS OPTIONAL AND THAT IS PRECISELY WHAT LET IT SHIP: `tsc` cannot name a
746
+ call site that forgot an optional prop. It STAYS optional because `manage-agent`
747
+ legitimately does not want it (an agent's reads never open a grid door, so the rule
748
+ would be stored and never applied) β€” so what pins this line is `verify_ui.py`'s
749
+ `metrics_toggle_has_a_mount_site`, not the type system.
750
+ ⚠ AND THIS COMMENT SITS HERE, NOT BETWEEN THE ATTRIBUTES, because a JSX comment in
751
+ the attribute list TYPE-CHECKS under `tsc --noEmit` and then EMITS a bare `...` into
752
+ the props object β€” invalid JS that only the render smoke catches. */}
753
  <ModulePermsList
754
  modules={payload.modules}
755
  entries={draft}
 
757
  onFilter={(key, next) => setDraft((d) => setFilter(d, key, next))}
758
  onToggleHidden={(key, fk) => setDraft((d) => toggleHidden(d, key, fk))}
759
  onSetHidden={(key, keys) => setDraft((d) => setHidden(d, key, keys))}
760
+ onMetrics={(key, on) => setDraft((d) => setMetrics(d, key, on))}
761
  userOptions={userOptions}
762
  emptyNote="This deployment declares no modules that can be restricted."
763
  headExtra={(m) =>