Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions memory/ui/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
* the console mounts and link-swaps it, styles-before-scripts on boot.
*
* `setup(host)` registers one contribution:
* - src/page/ — the `#/ext/memory` page: banks in a left rail, the selected
* bank's memories (pin/edit/tombstone in place), a schematic graph, the
* always-injected markdown rules, and a turn-preview dry run. Everything
* re-reads live off `memory::item-changed` / `memory::bank-changed`
* (poll fallback while the bindings are unavailable).
* - src/page/ — the `#/ext/memory` page: banks in a navigation rail, the
* selected bank's workspace (always-injected markdown rules, memories with
* pin/edit/tombstone in place, a schematic graph, and a turn-preview dry
* run) behind one segmented control; a drill-in flow when the pane is
* narrow. Everything re-reads live off `memory::item-changed` /
* `memory::bank-changed` (poll fallback while the bindings are
* unavailable).
*
* No config-form override: memory's configuration is a flat list of typed
* scalars, which the console's schema-generated form already renders from
Expand All @@ -28,6 +30,6 @@ export default function setup(host: Host) {
host.pages.register({
id: 'memory',
title: 'memory',
render: () => <MemoryPage host={host} />,
render: (props) => <MemoryPage host={host} {...props} />,
})
}
88 changes: 61 additions & 27 deletions memory/ui/src/page/BankRail.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
/**
* Navigation column: the bank list (first-class named memory scopes) plus
* an inline create form. Selecting a bank scopes the rules/memories/graph/
* preview workspace; sessions pick their bank via session metadata
* `memory_bank`. In the narrow drill-in flow this list is the first pane.
*/

import { useState } from 'react'
import { Button, Input } from '@iii-dev/console-ui'
import { Plus } from './icons'
import type { MemoryBank } from './memory-data'

/**
* Left rail: the bank list (first-class named memory scopes) plus an
* inline create form. Selecting a bank scopes the memories/rules/recall
* panels; sessions pick their bank via session metadata `memory_bank`.
*/

interface BankRailProps {
banks: MemoryBank[]
selected: string | null
/** True while the page-level refresh is in flight (skeleton on first load). */
loading: boolean
onSelect: (bank: string) => void
onCreate: (name: string) => Promise<boolean>
creating: boolean
Expand All @@ -20,36 +23,67 @@ interface BankRailProps {
export function BankRail({
banks,
selected,
loading,
onSelect,
onCreate,
creating,
}: BankRailProps) {
const [draft, setDraft] = useState('')
const valid = /^[a-z0-9][a-z0-9_-]{0,63}$/.test(draft)
const initialLoad = loading && banks.length === 0

return (
<aside className="mem-rail">
<div className="mem-rail-head">
<span className="mem-rail-caption">banks</span>
</div>
<div className="mem-rail-list">
{banks.map((bank) => (
<button
key={bank.name}
type="button"
onClick={() => onSelect(bank.name)}
className={`mem-rail-item${selected === bank.name ? ' active' : ''}`}
>
<span className="mem-rail-name">{bank.name}</span>
<span className="mem-rail-meta">
{bank.memories} memories · {bank.pinned} pinned · {bank.rules}{' '}
rules
</span>
</button>
))}
<aside className="mem-ui-rail" aria-label="bank list">
<header className="mem-ui-col-head">
<span className="label">banks</span>
<span className="spacer" />
{initialLoad ? null : <span className="count">{banks.length}</span>}
</header>
<div className="mem-ui-rail-scroll">
{initialLoad ? (
<div className="mem-ui-rail-skel" aria-hidden>
{[
[60, 90],
[40, 80],
[70, 90],
].map(([a, b]) => (
<div key={`${a}-${b}`} className="mem-ui-skel-row">
<span className={`bar w${a}`} />
<span className={`bar w${b}`} />
</div>
))}
</div>
) : banks.length === 0 ? (
<div className="mem-ui-rail-empty">
<p>No banks yet.</p>
<p className="dim">
Create one below, or just chat: the default bank materializes
when the first memory is saved.
</p>
</div>
) : (
<ul className="mem-ui-nav-list">
{banks.map((bank) => (
<li key={bank.name}>
<button
type="button"
className={`mem-ui-nav-row${selected === bank.name ? ' active' : ''}`}
aria-current={selected === bank.name ? 'true' : undefined}
onClick={() => onSelect(bank.name)}
>
<span className="name">{bank.name}</span>
<span className="fine">
{bank.memories} memories · {bank.pinned} pinned ·{' '}
{bank.rules} rules
</span>
</button>
</li>
))}
</ul>
)}
</div>
<form
className="mem-rail-form"
className="mem-ui-rail-form"
onSubmit={(e) => {
e.preventDefault()
if (!valid || creating) return
Expand All @@ -63,7 +97,7 @@ export function BankRail({
onChange={setDraft}
placeholder="new bank"
aria-label="new bank name"
className="mem-rail-input"
className="mem-ui-rail-input"
/>
<Button
type="submit"
Expand Down
Loading