From 1bf6bcb2a06b5c0774b6111d86ee1d18f73d7a00 Mon Sep 17 00:00:00 2001 From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:51:45 -0700 Subject: [PATCH 1/4] Improve Memory Hub loading, navigation, and question readers Add compact inventory projections and reliable RPC callback ownership, simplify graph payloads, and unify Inbox, Views, Questions, and source layouts. Add dedicated question routes, responsive cards, synopsis sidecards, and consistent accessible icon actions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/packages/agentRpc/README.md | 9 + ts/packages/agentRpc/src/client.ts | 62 ++- .../agentRpc/test/actionContext.spec.ts | 98 ++++ ts/packages/agents/browser/README.md | 77 ++- .../browser/src/agent/memoryHubGraph.mts | 13 +- .../src/views/client/library/memoryHub.css | 229 +++++--- .../src/views/client/library/memoryHub.html | 68 +-- .../views/client/library/memoryHub.test.ts | 90 ++++ .../src/views/client/library/memoryHub.ts | 107 +++- .../client/library/memoryHubEditorial.test.ts | 3 + .../client/library/memoryHubExplore.test.ts | 1 + .../views/client/library/memoryHubExplore.ts | 24 +- .../views/client/library/memoryHubGraph.css | 22 +- .../client/library/memoryHubGraph.test.ts | 349 +++++++------ .../views/client/library/memoryHubGraph.ts | 191 ++----- .../client/library/memoryHubInbox.test.ts | 192 +++++++ .../views/client/library/memoryHubInbox.ts | 256 +++++++++ .../library/memoryHubMaintenance.test.ts | 10 + .../client/library/memoryHubMaintenance.ts | 99 ++-- .../client/library/memoryHubModel.test.ts | 7 + .../views/client/library/memoryHubModel.ts | 7 + .../library/memoryHubProjectBrief.test.ts | 98 +++- .../client/library/memoryHubQuestions.test.ts | 209 +++++++- .../client/library/memoryHubQuestions.ts | 368 +++++++++---- .../client/library/memoryHubQuestionsModel.ts | 6 +- .../client/library/memoryHubTimeline.test.ts | 4 +- .../views/client/library/memoryHubUi.test.ts | 15 + .../src/views/client/library/memoryHubUi.ts | 10 + .../client/library/memoryHubUsability.test.ts | 2 +- .../library/memoryHubViewActivity.test.ts | 60 ++- .../client/library/memoryHubViewActivity.ts | 66 ++- .../client/library/memoryHubViews.test.ts | 448 ++++++++++++++-- .../views/client/library/memoryHubViews.ts | 490 ++++++++++++------ .../browser/test/memoryHubGraph.test.mjs | 43 ++ .../src/memoryGraphViewTypes.ts | 5 +- .../memory/client/src/inventoryProtocol.ts | 3 + .../client/test/schemaViewProtocol.spec.mjs | 36 ++ ts/packages/memory/service/README.md | 13 + .../memory/service/src/fileMemoryService.ts | 14 +- .../memory/service/src/inventoryTypes.ts | 2 + .../memory/service/src/viewReadProjection.ts | 10 + ts/packages/memory/service/src/viewStore.ts | 3 +- .../memory/service/test/schemaViews.spec.ts | 78 +++ .../service/test/viewIndexConcurrency.spec.ts | 143 +++-- 44 files changed, 3172 insertions(+), 868 deletions(-) create mode 100644 ts/packages/agents/browser/src/views/client/library/memoryHubInbox.test.ts create mode 100644 ts/packages/agents/browser/src/views/client/library/memoryHubInbox.ts diff --git a/ts/packages/agentRpc/README.md b/ts/packages/agentRpc/README.md index 192f6f2144..47d747bc61 100644 --- a/ts/packages/agentRpc/README.md +++ b/ts/packages/agentRpc/README.md @@ -2,6 +2,15 @@ This package contains code to support remoting TypeAgent SDK interfaces via an abstract RpcChannel interface. +## Initialization option callbacks + +An agent process can serve multiple conversation contexts. Initialization options +containing functions use a shared options RPC channel, with references retained +per initialized context (including initializations still in flight). Closing one +context releases only its options reference; the channel remains available to +other contexts. The last release closes the channel, and subsequent initialization +can create it again. Failed initialization also releases its reference. + ## Connection convention: prefer `rpc.rebind` over recreate **Decision (adopted):** rpc connection clients should be built as a **durable diff --git a/ts/packages/agentRpc/src/client.ts b/ts/packages/agentRpc/src/client.ts index fe5e056fa4..77ae9eb132 100644 --- a/ts/packages/agentRpc/src/client.ts +++ b/ts/packages/agentRpc/src/client.ts @@ -262,6 +262,8 @@ export async function createAgentRpcClient( const actionContextMap = createObjectMap>(); let optionsRpc: ReturnType | undefined; + const optionsReferences = new Map(); + const contextOptions = new Map(); function getOptionsCallBack(initOptions?: any) { const functions = getOptionsFunctions(initOptions); if (functions === undefined) { @@ -270,11 +272,28 @@ export async function createAgentRpcClient( if (optionsRpc === undefined) { optionsRpc = createOptionsRpc(channelProvider, name, options); } + optionsReferences.set( + initOptions, + (optionsReferences.get(initOptions) ?? 0) + 1, + ); return { id: optionsRpc.optionsMap.getId(initOptions), functions, }; } + function releaseOptions(initOptions: unknown) { + const remaining = (optionsReferences.get(initOptions) ?? 0) - 1; + if (remaining > 0) { + optionsReferences.set(initOptions, remaining); + return; + } + optionsReferences.delete(initOptions); + optionsRpc?.optionsMap.close(initOptions); + if (!optionsReferences.size && optionsRpc) { + channelProvider.deleteChannel(getOptionsChannelName(name, options)); + optionsRpc = undefined; + } + } function withActionContext( actionContext: ActionContext, fn: (contextParams: ActionContextParams) => T, @@ -674,11 +693,30 @@ export async function createAgentRpcClient( } const agent: Required = { - initializeAgentContext(settings?: AppAgentInitSettings) { - return rpc.invoke("initializeAgentContext", { - settings, - optionsCallBack: getOptionsCallBack(settings?.options), - }); + async initializeAgentContext(settings?: AppAgentInitSettings) { + const optionsCallBack = getOptionsCallBack(settings?.options); + try { + const context = await rpc.invoke("initializeAgentContext", { + settings, + optionsCallBack, + }); + if (optionsCallBack) { + if ( + typeof context !== "object" || + context === null || + !("contextId" in context) || + typeof context.contextId !== "number" + ) + throw new Error( + "Agent initialization did not return a context ID", + ); + contextOptions.set(context.contextId, settings?.options); + } + return context; + } catch (error) { + if (optionsCallBack) releaseOptions(settings?.options); + throw error; + } }, updateAgentContext( enable, @@ -915,12 +953,14 @@ export async function createAgentRpcClient( regIdsByContext.delete(contextId); } contextMap.close(context); - // Clean up the options RPC channel once this agent context is closed. - // Options are agent-scoped (created once per initializeAgentContext call) - // so they can be released when the context is torn down. - if (optionsRpc !== undefined) { - channelProvider.deleteChannel(getOptionsChannelName(name, options)); - optionsRpc = undefined; + const agentContextId = context.agentContext?.contextId; + if ( + agentContextId !== undefined && + contextOptions.has(agentContextId) + ) { + const initOptions = contextOptions.get(agentContextId); + contextOptions.delete(agentContextId); + releaseOptions(initOptions); } return result; }; diff --git a/ts/packages/agentRpc/test/actionContext.spec.ts b/ts/packages/agentRpc/test/actionContext.spec.ts index d8264d4f51..b4e50f5516 100644 --- a/ts/packages/agentRpc/test/actionContext.spec.ts +++ b/ts/packages/agentRpc/test/actionContext.spec.ts @@ -14,10 +14,108 @@ import { import { createAgentRpcServer } from "../src/server.js"; import { ChoiceManager, + createActionResultFromTextDisplay, createYesNoChoiceResult, } from "@typeagent/agent-sdk/helpers/action"; describe("agent action context RPC", () => { + test.each([false, true])( + "closing a context preserves surviving option callbacks (shared options: %s)", + async (shared) => { + let clientProvider: ChannelProviderAdapter; + let serverProvider: ChannelProviderAdapter; + clientProvider = createChannelProviderAdapter( + "options-client", + (message, callback) => { + queueMicrotask(() => + serverProvider.notifyMessage( + JSON.parse(JSON.stringify(message)), + ), + ); + callback?.(null); + }, + ); + serverProvider = createChannelProviderAdapter( + "options-server", + (message, callback) => { + queueMicrotask(() => + clientProvider.notifyMessage( + JSON.parse(JSON.stringify(message)), + ), + ); + callback?.(null); + }, + ); + const deletedChannels: string[] = []; + const deleteChannel = clientProvider.deleteChannel; + clientProvider.deleteChannel = (name) => { + deletedChannels.push(name); + deleteChannel(name); + }; + type Options = { read(): string; fail?: boolean }; + const agent: AppAgent = { + initializeAgentContext: async (settings) => { + const options = settings?.options as Options; + if (options.fail) throw new Error("Initialization failed"); + return options; + }, + closeAgentContext: async () => {}, + executeAction: async (_, context: ActionContext) => + createActionResultFromTextDisplay( + await context.sessionContext.agentContext.read(), + ), + }; + const server = createAgentRpcServer( + "retained-options", + agent, + serverProvider, + ); + const client = await createAgentRpcClient( + "retained-options", + clientProvider, + server.agentInterface, + ); + const first = { read: () => "first" }; + const second = shared ? first : { read: () => "second" }; + let sessionId = 0; + const initialize = async (options: Options) => + ({ + sessionContextId: `options-${++sessionId}`, + agentContext: await client.initializeAgentContext!({ + options, + }), + }) as SessionContext; + const read = (sessionContext: SessionContext) => + client.executeAction!( + { schemaName: "test", actionName: "read" }, + { sessionContext } as ActionContext, + ); + try { + const a = await initialize(first); + const b = await initialize(second); + await expect( + initialize({ read: () => "", fail: true }), + ).rejects.toThrow("Initialization failed"); + await client.closeAgentContext!(a); + expect(deletedChannels).toEqual([]); + await expect(read(b)).resolves.toMatchObject({ + displayContent: shared ? "first" : "second", + }); + await client.closeAgentContext!(b); + expect(deletedChannels).toContain("options:retained-options"); + const c = await initialize(first); + await expect(read(c)).resolves.toMatchObject({ + displayContent: "first", + }); + await client.closeAgentContext!(c); + } finally { + server.closeFn(); + clientProvider.notifyDisconnected(); + serverProvider.notifyDisconnected(); + } + }, + ); + test("invokes nested initialization option methods with their receiver", async () => { let clientProvider: ChannelProviderAdapter; let serverProvider: ChannelProviderAdapter; diff --git a/ts/packages/agents/browser/README.md b/ts/packages/agents/browser/README.md index 2c30958fa5..0fc40d48a2 100644 --- a/ts/packages/agents/browser/README.md +++ b/ts/packages/agents/browser/README.md @@ -39,8 +39,15 @@ separately. Updating Puppeteer does not patch system Chrome. ### Scoped graphs and Memory Hub navigation Memory Hub Explore supports entity, topic and maintained hierarchy graphs with -document/event filters, bounded generation-aware paging, coverage diagnostics and -exact evidence drilldown. Graph preparation happens separately from reads; hosts +document/event filters, bounded graph previews and exact evidence drilldown. +The entity and topic pages show filters, graph controls and the canvas without +coverage disclosures, collection/count paging rows, browse lists or routine +maintenance status messages. Loading, empty states and request failures remain +visible. Refresh, Global and Fit are Font Awesome icon buttons with accessible +labels and tooltips, grouped inside the upper-left corner of the graph canvas. +The graph response omits coverage, counts, limits and inventory paging +metadata; topic generation is retained for snapshot-safe evidence drilldown. +Graph preparation happens separately from reads; hosts without the scoped capability report it explicitly. Retained originals and event bodies remain subject to current eligibility and suppression checks. @@ -48,9 +55,69 @@ Views use paginated cards and a separate detail reader, with build receipts in a flyout. Grouped activity, Inbox navigation and responsive detail panels reuse summary inventories rather than fetching every view's full evidence. These navigation features do not depend on checkpoint or integrated-answer APIs. +Inventory pages contain up to 25 summaries per collection, not 25 globally. +Inbox has counted tabs for Question decisions, Views, and Sources & operations. +Its All overview shows at most two compact rows per category, with Questions +first; full explanations and existing actions open in a details side panel. +Review uses a file-lines icon button, and each All-overview section's View all +action uses a list icon button. Both retain descriptive accessible names and +tooltips. Views uses a bell for Latest build, retaining its spinner while a build +is running. +Counts describe loaded attention items/groups, with `+` when more may be +available, rather than claiming a complete cross-collection total. Switching +tabs or opening an explanation does not fetch another evidence inventory. +Activity and Inbox use summary conflict state where available; full conflict +evidence is reserved for selected details or older-service compatibility. + +Explore, Inbox, Sources, Views, Questions and Activity share icon-and-underline +navigation styling. Active filters retain their pressed state; actual tab panels +retain their selected state. Tab strips scroll horizontally rather than wrapping +or widening the page on smaller screens. Views exposes its existing type filter +as tabs above search and build controls, preserving paginated inventory queries, +search text, keyboard focus and horizontal scroll position when it refreshes. +Supported Views tabs are ordered All, Knowledge Wiki, Project Brief, Timeline +and Troubleshooting Guide. Questions has its own left-navigation entry rather +than a duplicate Views filter tab. Collection-maintenance entry points use the +screwdriver-and-wrench icon. + +Views and Questions place those tabs directly after the page title. Their lists, +search controls and bottom pagination live in Sources-style surface cards with +server-reported totals in the upper-right corner, not loaded-page counts. +Unavailable question inventories are marked unavailable or partial rather than +displayed as a complete zero. Knowledge preparation remains lazy and is placed +at the bottom of Questions. Explore Overview has no separate refresh button; +scope changes and the main refresh action retain their existing behavior. + +Wiki and Project Brief readers distinguish synopsis and coverage caveats from +their main sections in a blue-tinted right-hand infobox, alongside version, +completeness and revision +metadata. Article text wraps beside the infobox on wide screens; at 800 px and +below the panel stacks above the article. Source disclosures, section navigation +and edit actions remain available. Project Brief goals, owners, status, milestones, +decisions, risks and context stay in the main reading flow; other view types retain +their layout. +Reader Back and Edit controls are icon-only with accessible names and tooltips. +The overflow control opens the shared icon-and-text action menu, including +conditional conflict review and generated-update unlocks. Escape dismisses the +menu; closing an action dialog returns focus to the overflow trigger. + +Views shows a spinner and loading message inside the list card during type +changes, hiding stale results and paging until the new inventory settles. +Rapid type changes queue the latest filter rather than dropping it while another +read is in flight. Failed reads remain explicit and do not display a misleading +empty result. ### Schema-defined questions +The Questions landing page uses two columns of compact cards, stacking into one +column at 800 px and below. A card opens a dedicated question reader at a +corpus/view/slot deep link. Answer candidates and evidence stay in the main content, +while an option B sidecard groups coverage, review/admission state, view version +and evaluated-source metadata. The reader retains guarded edits, explicit errors, +retry, history and exact evidence. Back returns to the previous scoped inventory +with its filters and paging; direct links can load slots outside the current page +without scanning every collection. + The Memory hub's Questions workspace is a complete projection of versioned schema-view slots, including proposed and rejected slots. The inspector reviews candidates and exact source revisions, records schema-validated user assertions, @@ -72,8 +139,10 @@ Markdown links preserve exact revisions, slots and collection item keys when opened in the browser. Optional service capabilities and operation errors are reported explicitly, not interpreted as empty inventories. Question lists use bounded summary pages; selecting a question separately loads its full evidence. -Questions also mounts knowledge preparation status, discovery policy controls, -bounded failed-discovery retries and captured readiness checkpoints. Existing +Questions loads knowledge preparation status, discovery policy controls, +bounded failed-discovery retries and captured readiness checkpoints. This +progress is fetched only when the Knowledge preparation disclosure is expanded, +not when loading question lists or Inbox attention. Existing corpora remain off until enabled; new corpora default to source observed. Policy admission is not human confirmation, and source indexing alone never implies knowledge readiness. Integrated search answers retain original-source diff --git a/ts/packages/agents/browser/src/agent/memoryHubGraph.mts b/ts/packages/agents/browser/src/agent/memoryHubGraph.mts index 07fa39667e..1bfe4dcdcd 100644 --- a/ts/packages/agents/browser/src/agent/memoryHubGraph.mts +++ b/ts/packages/agents/browser/src/agent/memoryHubGraph.mts @@ -158,10 +158,21 @@ export function createMemoryHubGraphFunctions( Math.floor(maxEdges / selected.length), ), }; + const graph = await timed(query.call(service, input)); result.corpora.push({ corpusId: corpus.corpusId, corpusName: corpus.name, - graph: await timed(query.call(service, input)), + graph: { + generation: graph.generation, + nodes: graph.nodes, + edges: graph.edges, + ...(graph.coverage.topics?.generation + ? { + topicGeneration: + graph.coverage.topics.generation, + } + : {}), + }, }); } catch (error) { const message = diff --git a/ts/packages/agents/browser/src/views/client/library/memoryHub.css b/ts/packages/agents/browser/src/views/client/library/memoryHub.css index e3c7081386..94c6ace133 100644 --- a/ts/packages/agents/browser/src/views/client/library/memoryHub.css +++ b/ts/packages/agents/browser/src/views/client/library/memoryHub.css @@ -19,10 +19,27 @@ body[data-memory-hub] { } .hub-draft-views { - margin-top: 20px; - padding: 16px; - border: 1px solid var(--border); - border-radius: 8px; + min-width: 0; +} +.hub-draft-views > p:empty { + display: none; +} +.hub-draft-views .hub-tabs > button { + margin: 0 0 -1px; +} +.hub-list-card { + min-width: 0; +} +.hub-list-card > .section-heading { + margin-bottom: 12px; +} +.hub-list-card > .section-heading h2 { + margin: 0; +} +.hub-list-total { + color: var(--primary-dark); + font-size: 13px; + white-space: nowrap; } .hub-draft-views fieldset label { display: flex; @@ -70,8 +87,12 @@ body[data-memory-hub] { gap: 8px; margin: 12px 0; } -.hub-view-actions > details { - position: relative; +.hub-view-actions button { + margin: 0; +} +.hub-view-actions .hub-menu { + width: max-content; + max-width: min(320px, calc(100vw - 64px)); } .hub-view-document { max-width: 900px; @@ -104,6 +125,64 @@ body[data-memory-hub] { color: var(--muted); font-size: 13px; } +.hub-view-reading-flow { + display: flow-root; +} +.hub-view-infobox { + box-sizing: border-box; + float: right; + width: 310px; + max-width: 43%; + margin: 0 0 24px 28px; + padding: 20px; + border: 1px solid #cbdce8; + border-top: 4px solid #7599b6; + border-radius: 8px; + background: #edf5fa; + color: #3d5870; + font-size: 12px; + line-height: 1.75; +} +.hub-view-infobox > h3 { + margin: 0 0 6px; + color: #345b78; + font-size: 16px; +} +.hub-view-infobox-label { + margin: 0 0 14px; + color: #526e83; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; +} +.hub-view-infobox .hub-view-lead > :first-child { + margin-top: 0; +} +.hub-view-infobox .hub-view-lead > :last-child { + margin-bottom: 0; +} +.hub-view-infobox > .hub-view-note, +.hub-view-infobox > .hub-view-meta { + margin: 16px 0 0; + padding-top: 14px; + border-top: 1px solid #cddfea; + color: #526e83; + font-size: 11px; +} +.hub-view-infobox > .hub-view-meta { + display: block; +} +.hub-view-infobox .hub-chips { + margin-bottom: 8px; +} +@media (max-width: 800px) { + .hub-view-infobox { + float: none; + width: 100%; + max-width: none; + margin: 0 0 24px; + } +} .hub-view-section > h3 { margin: 20px 0 8px; font-size: 15px; @@ -153,22 +232,6 @@ body[data-memory-hub] { clip: rect(0 0 0 0); white-space: nowrap; } -.hub-view-more-actions > summary { - display: inline-grid; - place-items: center; - width: 32px; - height: 32px; - border-radius: 6px; - list-style: none; - cursor: pointer; -} -.hub-view-more-actions > summary::-webkit-details-marker { - display: none; -} -.hub-view-more-actions > summary:hover { - background: var(--surface-alt); -} -.hub-draft-views:has(.hub-view-document) > h3, .hub-draft-views:has(.hub-view-document) > p, .hub-draft-views:has(.hub-view-document) > .hub-view-build-receipt { display: none; @@ -192,7 +255,6 @@ body[data-memory-hub] { margin: 16px 0; } @media (max-width: 600px) { - .hub-draft-views, .hub-view-document { padding: 12px; } @@ -543,14 +605,20 @@ body[data-memory-hub] { /* Tabs: underline style for both buttons and links */ .hub-tabs { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; gap: 2px; + max-width: 100%; + min-width: 0; margin: 0 0 14px; border-bottom: 1px solid var(--border); + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; } .hub-tabs > button, .hub-tabs > a { display: inline-flex; + flex: 0 0 auto; align-items: center; gap: 7px; min-height: 0; @@ -562,6 +630,7 @@ body[data-memory-hub] { background: transparent; color: var(--muted); text-decoration: none; + white-space: nowrap; } .hub-tabs > button:hover:not(:disabled), .hub-tabs > a:hover { @@ -569,12 +638,17 @@ body[data-memory-hub] { color: var(--text); } .hub-tabs > button[aria-selected="true"], +.hub-tabs > button[aria-selected="true"]:hover, +.hub-page .hub-tabs > button[aria-pressed="true"], .hub-tabs > a[aria-current="page"] { border-bottom-color: var(--primary); background: transparent; color: var(--primary-dark); font-weight: 650; } +.hub-tabs > :focus-visible { + outline-offset: -3px; +} .hub-explore-nav { margin-top: 0; } @@ -717,6 +791,62 @@ textarea:focus-visible { } /* Inbox */ +.hub-inbox-tabs { + position: sticky; + top: 0; + z-index: 2; + background: var(--background); +} +.hub-inbox-workspace [role="tabpanel"][hidden] { + display: none; +} +.hub-inbox-overview-group { + margin: 8px 0 16px; +} +.hub-inbox-overview-head { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.hub-inbox-overview-head h3 { + margin: 0; +} +.hub-inbox-item { + display: flex; + align-items: center; + gap: 12px; + margin: 6px 0; + padding: 10px 12px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 8px; +} +.hub-inbox-item-text { + flex: 1; + min-width: 0; +} +.hub-inbox-item h4 { + margin: 0; + font-size: 14px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hub-inbox-item p { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + overflow-wrap: anywhere; + margin: 3px 0 0; + font-size: 12px; + color: var(--muted); +} +.hub-inbox-item > button { + flex: 0 0 auto; +} .inbox-card { display: flex; align-items: center; @@ -1222,10 +1352,13 @@ textarea:focus-visible { max-height: none; } -/* Questions: list on the left, selected question beside it */ +/* Questions: card inventory and a separate detail reader */ .hub-qhead { flex-wrap: wrap; } +.hub-qhead [role="status"]:empty { + display: none; +} .hub-qfilters { display: flex; flex-wrap: wrap; @@ -1275,16 +1408,10 @@ textarea:focus-visible { .hub-notice i { margin-right: 8px; } -.hub-split { - display: grid; - grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr); - gap: 16px; - align-items: start; -} .hub-qlist { - display: flex; - flex-direction: column; - gap: 6px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; min-width: 0; } .hub-qrow { @@ -1329,30 +1456,14 @@ textarea:focus-visible { white-space: nowrap; } .hub-qdetail { - position: sticky; - top: 8px; - max-height: calc(100vh - 24px); min-width: 0; - overflow: auto; - padding: 0 4px 16px; -} -.hub-qdetail:empty::before { - content: "Select a question to see its answers."; - color: var(--muted); -} -.hub-qdetail-head { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; } -.hub-qdetail-head h3 { - flex: 1 1 100%; - margin: 0; - font-size: 16px; +.hub-question-content { + display: flow-root; } -.hub-qdetail-head .hub-row-actions { - margin-left: auto; +.hub-question-reader .hub-view-actions { + max-width: 900px; + margin: 0 auto 8px; } .hub-qdetail details { margin: 10px 0; @@ -1384,14 +1495,10 @@ textarea:focus-visible { cursor: pointer; font-weight: 600; } -@media (max-width: 900px) { - .hub-split { +@media (max-width: 800px) { + .hub-qlist { grid-template-columns: minmax(0, 1fr); } - .hub-qdetail { - position: static; - max-height: none; - } } .hub-qdetail .card .hub-row-actions { display: flex; diff --git a/ts/packages/agents/browser/src/views/client/library/memoryHub.html b/ts/packages/agents/browser/src/views/client/library/memoryHub.html index f6c8cc7720..d3a8b09c9f 100644 --- a/ts/packages/agents/browser/src/views/client/library/memoryHub.html +++ b/ts/packages/agents/browser/src/views/client/library/memoryHub.html @@ -101,42 +101,44 @@

Memory

Inbox

-
-
- +
+
+
+ +
+
+
+ + + +
-
-
- - - -