Deploy AIOS web (React glide grid + FastAPI slice)
Browse files- RELEASES.json +1 -1
- VERSION +1 -1
- api/routes_alerts.py +5 -1
- web/src/assistant/AssistantPage.tsx +29 -11
- web/src/customer-grid/ColumnMenu.tsx +1 -1
- web/src/home/HomePage.tsx +10 -1
- web/src/shell/Shell.tsx +10 -4
RELEASES.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
{
|
| 2 |
-
"current": "
|
| 3 |
"releases": [
|
| 4 |
{
|
| 5 |
"version": "v25",
|
|
|
|
| 1 |
{
|
| 2 |
+
"current": "6d8d6ae",
|
| 3 |
"releases": [
|
| 4 |
{
|
| 5 |
"version": "v25",
|
VERSION
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
|
|
|
|
| 1 |
+
6d8d6ae
|
api/routes_alerts.py
CHANGED
|
@@ -193,7 +193,11 @@ def notification_view(item):
|
|
| 193 |
if kind == NOTIF_KIND_SHARE:
|
| 194 |
sender = actor or "A teammate"
|
| 195 |
elif kind == NOTIF_KIND_AUTOMATION:
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
else:
|
| 198 |
sender = actor or "Alerts"
|
| 199 |
out = {**item, "read": bool(item.get("read")), "kind": kind,
|
|
|
|
| 193 |
if kind == NOTIF_KIND_SHARE:
|
| 194 |
sender = actor or "A teammate"
|
| 195 |
elif kind == NOTIF_KIND_AUTOMATION:
|
| 196 |
+
# β "Agents", not "Automation" (W34-T40, corrected at QA 2026-08-17). The client's
|
| 197 |
+
# `senderOf` already falls back to `AGENTS_MODULE_LABEL` β but `if (sent) return sent`
|
| 198 |
+
# runs FIRST, so this server literal won and every actor-less automation notification
|
| 199 |
+
# showed the retired module name in the inbox's From column.
|
| 200 |
+
sender = actor or "Agents"
|
| 201 |
else:
|
| 202 |
sender = actor or "Alerts"
|
| 203 |
out = {**item, "read": bool(item.get("read")), "kind": kind,
|
web/src/assistant/AssistantPage.tsx
CHANGED
|
@@ -54,12 +54,16 @@ const isMode = (value: unknown): value is AssistantMode => value === "chat" || v
|
|
| 54 |
* B's to own. The arrival parameter is consumed once, below, and stored.
|
| 55 |
* Pure and exported so the decision can be checked without a browser.
|
| 56 |
*/
|
| 57 |
-
export function
|
| 58 |
const mark = hash.indexOf("?");
|
| 59 |
-
if (mark
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
return isMode(stored) ? stored : "chat";
|
| 64 |
}
|
| 65 |
|
|
@@ -352,14 +356,28 @@ export default function AssistantPage({ granted }: AssistantPageProps) {
|
|
| 352 |
* by following a `#/query` link, never touching the toggle, would have the parameter stripped
|
| 353 |
* here and then land back in Chat on the next reload, restored from a choice made days earlier.
|
| 354 |
* Following the link IS choosing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
*/
|
| 356 |
useEffect(() => {
|
| 357 |
-
if (typeof window === "undefined"
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
}, []);
|
| 364 |
|
| 365 |
const pickMode = useCallback((next: AssistantMode) => {
|
|
|
|
| 54 |
* B's to own. The arrival parameter is consumed once, below, and stored.
|
| 55 |
* Pure and exported so the decision can be checked without a browser.
|
| 56 |
*/
|
| 57 |
+
export function arrivalMode(hash: string): AssistantMode | null {
|
| 58 |
const mark = hash.indexOf("?");
|
| 59 |
+
if (mark < 0) return null;
|
| 60 |
+
const asked = new URLSearchParams(hash.slice(mark + 1)).get("mode");
|
| 61 |
+
return isMode(asked) ? asked : null;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
export function initialMode(hash: string, stored: string | null): AssistantMode {
|
| 65 |
+
const asked = arrivalMode(hash);
|
| 66 |
+
if (asked) return asked;
|
| 67 |
return isMode(stored) ? stored : "chat";
|
| 68 |
}
|
| 69 |
|
|
|
|
| 356 |
* by following a `#/query` link, never touching the toggle, would have the parameter stripped
|
| 357 |
* here and then land back in Chat on the next reload, restored from a choice made days earlier.
|
| 358 |
* Following the link IS choosing.
|
| 359 |
+
*
|
| 360 |
+
* ββ AND IT LISTENS FOR `hashchange`, WHICH IS THE DEFECT THE OWNER REPORTED (QA 2026-08-17,
|
| 361 |
+
* measured on the deployed build). `#/query` and `#/assistant` resolve to the SAME route, so
|
| 362 |
+
* following a `#/query` link from anywhere inside the running app is a same-document hash
|
| 363 |
+
* change: this component is already mounted, `useState(readMode)` does not run a second time,
|
| 364 |
+
* and the panel stayed on Chat while the address bar read `?mode=query`. Only a full reload
|
| 365 |
+
* ever showed Query. **A mount-time read cannot see an arrival that does not remount** β the
|
| 366 |
+
* listener is the whole fix, and `arrivalMode` is the ONE parser both paths call so a later
|
| 367 |
+
* change cannot make the two disagree.
|
| 368 |
*/
|
| 369 |
useEffect(() => {
|
| 370 |
+
if (typeof window === "undefined") return;
|
| 371 |
+
const consume = () => {
|
| 372 |
+
const asked = arrivalMode(window.location.hash);
|
| 373 |
+
if (!asked) return;
|
| 374 |
+
setMode(asked);
|
| 375 |
+
storeMode(asked);
|
| 376 |
+
window.history.replaceState(null, "", `#/${ASSISTANT_ROUTE}`);
|
| 377 |
+
};
|
| 378 |
+
consume();
|
| 379 |
+
window.addEventListener("hashchange", consume);
|
| 380 |
+
return () => window.removeEventListener("hashchange", consume);
|
| 381 |
}, []);
|
| 382 |
|
| 383 |
const pickMode = useCallback((next: AssistantMode) => {
|
web/src/customer-grid/ColumnMenu.tsx
CHANGED
|
@@ -1647,7 +1647,7 @@ function ExtraTypeEditor({
|
|
| 1647 |
<div className="cg-field-hint">
|
| 1648 |
{dim.keyedBy === "id"
|
| 1649 |
? `Pick the column holding the Odoo ID for ${dim.label.toLowerCase()}. Matching on a name instead leaves every cell blank.`
|
| 1650 |
-
: `Pick the column holding the ${dim.label.toLowerCase()} value itself
|
| 1651 |
</div>
|
| 1652 |
) : null}
|
| 1653 |
<label>
|
|
|
|
| 1647 |
<div className="cg-field-hint">
|
| 1648 |
{dim.keyedBy === "id"
|
| 1649 |
? `Pick the column holding the Odoo ID for ${dim.label.toLowerCase()}. Matching on a name instead leaves every cell blank.`
|
| 1650 |
+
: `Pick the column holding the ${dim.label.toLowerCase()} value itself. This grouping keys by its own value, not by an ID.`}
|
| 1651 |
</div>
|
| 1652 |
) : null}
|
| 1653 |
<label>
|
web/src/home/HomePage.tsx
CHANGED
|
@@ -27,6 +27,7 @@ import { FolderMark } from "../customer-grid/icons";
|
|
| 27 |
// rather than reached across the fence. Same rule as the rail: consume the component that points
|
| 28 |
// at the generated artifact, never inline the path ([[loopable-nav-logo-toggle]] β one element
|
| 29 |
// paints the mark).
|
|
|
|
| 30 |
import { Mark } from "../shell/Brand";
|
| 31 |
import { dbChipClass } from "../shell/nav";
|
| 32 |
import type { DatabaseEntry, Recent } from "../shell/nav";
|
|
@@ -370,7 +371,15 @@ export default function HomePage({
|
|
| 370 |
here", which is a different and false statement. */}
|
| 371 |
{automations.length ? (
|
| 372 |
<section className="home-section">
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
<div className={"home-tiles is-" + layout}>
|
| 375 |
{automations.map((a) => (
|
| 376 |
<button
|
|
|
|
| 27 |
// rather than reached across the fence. Same rule as the rail: consume the component that points
|
| 28 |
// at the generated artifact, never inline the path ([[loopable-nav-logo-toggle]] β one element
|
| 29 |
// paints the mark).
|
| 30 |
+
import { AGENTS_MODULE_LABEL } from "../inbox/inboxModel";
|
| 31 |
import { Mark } from "../shell/Brand";
|
| 32 |
import { dbChipClass } from "../shell/nav";
|
| 33 |
import type { DatabaseEntry, Recent } from "../shell/nav";
|
|
|
|
| 371 |
here", which is a different and false statement. */}
|
| 372 |
{automations.length ? (
|
| 373 |
<section className="home-section">
|
| 374 |
+
{/* β W34-T40's `done-when` names FOUR surfaces that must read Agents, and this was the
|
| 375 |
+
one that did not: a hardcoded `"Automations"` that never consulted the module label.
|
| 376 |
+
QA 2026-08-17. The rail row and the page heading both read `.label` off the registry
|
| 377 |
+
entry, which is why they moved and this did not.
|
| 378 |
+
β It takes the SHARED constant, not a second literal spelling it correctly today.
|
| 379 |
+
The gate that was supposed to catch this pinned the string `Automations</h2>` and so
|
| 380 |
+
REQUIRED the defect β [[a-gate-that-pins-a-spelling-not-a-claim]]. Re-typing "Agents"
|
| 381 |
+
here would have left the next rename in exactly the same trap. */}
|
| 382 |
+
<h2 className="home-section-title">{AGENTS_MODULE_LABEL}</h2>
|
| 383 |
<div className={"home-tiles is-" + layout}>
|
| 384 |
{automations.map((a) => (
|
| 385 |
<button
|
web/src/shell/Shell.tsx
CHANGED
|
@@ -2000,9 +2000,14 @@ function ShellFrame() {
|
|
| 2000 |
Query did not lose its surface β it lost its RAIL ROW, because a toggle inside the
|
| 2001 |
assistant is now the way in. `#/query` still resolves and redirects (`W34-T15`), so
|
| 2002 |
every existing bookmark and every `openBuiltView` hand-off keeps working.
|
| 2003 |
-
β
|
| 2004 |
-
|
| 2005 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2006 |
|
| 2007 |
{/* WAVE 19 R10 / C3 β Automation, now AGENTS. An `<a>` to the route it already had: the
|
| 2008 |
surface, its rail and its editor are another session's tree this wave and
|
|
@@ -2606,7 +2611,8 @@ function ShellFrame() {
|
|
| 2606 |
{...(active.icon ? { icon: active.icon } : {})}
|
| 2607 |
/>
|
| 2608 |
<div className="shell-auto-host">
|
| 2609 |
-
|
|
|
|
| 2610 |
</div>
|
| 2611 |
</div>
|
| 2612 |
) : active?.kind === "native" ? (
|
|
|
|
| 2000 |
Query did not lose its surface β it lost its RAIL ROW, because a toggle inside the
|
| 2001 |
assistant is now the way in. `#/query` still resolves and redirects (`W34-T15`), so
|
| 2002 |
every existing bookmark and every `openBuiltView` hand-off keeps working.
|
| 2003 |
+
β THIS COMMENT ASSERTED SOMETHING FALSE FOR ABOUT A MINUTE, and the correction is
|
| 2004 |
+
kept because the habit is the useful part. It read *"`SearchIcon` survives as a
|
| 2005 |
+
symbol: the flyout's own filter box draws it"* β it does not, and `tsc` (TS6133) said
|
| 2006 |
+
so on the very next run. The function was deleted in the same edit; its tombstone is
|
| 2007 |
+
where it used to live, one screen up. **A claim about who calls a symbol is
|
| 2008 |
+
answerable in one command, so ask the compiler rather than reasoning about it.**
|
| 2009 |
+
Found again at the close-out dead-code sweep, which greps every deleted symbol and
|
| 2010 |
+
reads each survivor: this one was a stale CLAIM rather than a live caller. */}
|
| 2011 |
|
| 2012 |
{/* WAVE 19 R10 / C3 β Automation, now AGENTS. An `<a>` to the route it already had: the
|
| 2013 |
surface, its rail and its editor are another session's tree this wave and
|
|
|
|
| 2611 |
{...(active.icon ? { icon: active.icon } : {})}
|
| 2612 |
/>
|
| 2613 |
<div className="shell-auto-host">
|
| 2614 |
+
{/* USER-VISIBLE: ErrorBoundary renders "{surface} could not be shown" (T40, QA) */}
|
| 2615 |
+
<Lazily surface="Agents"><AutomationSurface /></Lazily>
|
| 2616 |
</div>
|
| 2617 |
</div>
|
| 2618 |
) : active?.kind === "native" ? (
|