feat(flows): frontend for the memory node — palette, config panel, i18n ×14 - #5228
Conversation
Adds the 13th tinyflows NodeKind (`memory`) to the editable Workflows canvas: NodeKind union + palette metadata (nodeKindMeta.ts), a dedicated config form (memoryFields.tsx) with per-operation progressive disclosure (recall/search/flavour/people/remember/forget), and i18n across all 14 locales. The config panel enforces the engine's hard invariant that a remember/forget node's scope may only ever be `flow`, both by never offering `user` in the select and by self-healing an already-invalid config on mount. Pure frontend (app/src/**) — no Rust or submodule changes; PR3 of tinyhumansai#5226.
📝 WalkthroughWalkthroughThe flow editor adds a ChangesMemory node frontend support
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
|
| Filename | Overview |
|---|---|
| app/src/components/flows/canvas/nodeConfig/memoryFields.tsx | New MemoryForm component with clean progressive disclosure, correct scope restriction for write ops, and a self-heal useEffect. Logic is well-tested and matches the engine invariant. |
| app/src/components/flows/canvas/nodeConfig/nodeConfigFields.tsx | Adds configNumber and NumberField primitives. configNumber has a correct isFinite guard on read; NumberField's onChange could emit NaN before that guard fires (see comment). |
| app/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsx | Minimal diff — imports MemoryForm and registers it in NODE_CONFIG_FORMS under the memory key. |
| app/src/lib/flows/nodeSummary.ts | Adds the memory case to describeNode with distinct branches for all six operations; search correctly returns "Searches memory" (previous thread concern resolved). |
| app/src/lib/flows/types.ts | Adds 'memory' to the NodeKind union with a clear doc comment; no other changes. |
| app/src/lib/flows/nodeKindMeta.ts | Appends the memory entry (🧠, sage, actions group) as the 13th kind in both the ordered list and the META record. |
| app/src/components/flows/canvas/nodeConfig/tests/memoryFields.test.tsx | 10-test suite covers per-operation field visibility, write-only flow scope, self-heal on mount, and round-trip for both numeric fields (limit and min_score). |
| app/src/lib/i18n/en.ts | Adds all 24 memory-node i18n keys (nodeKind + all nodeConfig.memory.*) in English; prose is clear and matches the component's field semantics. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[MemoryForm renders] --> B[Operation selector — always shown]
B --> C{operation}
C -->|recall / search / remember / forget| D[Scope selector]
C -->|flavour / people| E[No scope selector]
D --> F{isWrite?}
F -->|yes — remember/forget| G[Only 'flow' option]
F -->|no — recall/search| H[user · flow · flows options]
C -->|recall / search / people| I[Query field]
C -->|flavour| J[Flavour dropdown — 7 slugs]
C -->|remember / forget| K[Key field]
C -->|remember| L[Value field]
C -->|recall / search / people| M[Limit + Min-score NumberFields]
N[useEffect self-heal] -->|on mount/update| O{isWrite AND scope != 'flow'?}
O -->|yes| P[Emit onChange scope=flow]
O -->|no| Q[No-op]
Reviews (3): Last reviewed commit: "fix(flows): memory node PR review fixes ..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d62259160
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| 'transform', | ||
| 'output_parser', | ||
| 'sub_workflow', | ||
| 'memory', |
There was a problem hiding this comment.
Register the new memory-node surface in about_app
Adding memory to the palette makes workflow memory access a new user-facing flow-builder surface, but this commit does not update src/openhuman/about_app/, so the capability/privacy catalog and the in-app about/search surfaces stay unaware of it. The repo guidance explicitly requires updating that catalog when user-facing features are added or changed.
AGENTS.md reference: AGENTS.md:L378-L389
Useful? React with 👍 / 👎.
| hint={t('flows.nodeConfig.memory.flavourHint')} | ||
| value={configString(config, 'flavour')} | ||
| onChange={v => onChange({ flavour: v })} | ||
| placeholder="email-tone" |
There was a problem hiding this comment.
Use a valid flavour slug in the form hint
When an author selects the flavour operation and follows the UI example, email-tone is written into config.flavour, but the core flavour reader only accepts persona facets like communication, coding_style, stack, workflow, environment, directives, or anti_preferences and returns Unknown flavour for other strings (src/openhuman/memory/tools/flavour.rs). This makes the newly advertised form lead users to author runtime-invalid memory nodes; the placeholder/hint should use a valid slug or render a select of valid values.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/flows/canvas/nodeConfig/memoryFields.tsx`:
- Around line 194-212: Enforce valid numeric values in the onChange callbacks
for the memory limit and min_score NumberFields before calling onChange:
normalize or reject limit values below 1 or fractional values, and min_score
values outside 0..1. Preserve valid inputs and cover the boundary cases, rather
than relying on the visual min/max/step props.
In `@app/src/lib/flows/nodeSummary.ts`:
- Around line 102-104: Update the recall/search summary logic to return a
search-specific description when operation is 'search', while preserving the
existing “Recalls memory” text for recall and the scoped suffix behavior.
- Around line 95-104: Localize all new memory-node UI text: update the
operation-summary rendering around nodeSummary.ts lines 95-104 to use an
i18n-aware boundary or passed translation function, and in memoryFields.tsx
lines 153, 165, 175, and 186 replace the query, flavour, key, and value
placeholders with translation keys resolved via useT() from I18nContext. Add the
corresponding keys to every real locale file.
In `@app/src/lib/i18n/fr.ts`:
- Around line 4453-4457: Update the French hint values for
flows.nodeConfig.memory.queryOptionalHint and
flows.nodeConfig.memory.flavourHint to use consistent formal vous-register
wording and correct “style” to the plural “styles,” while preserving their
existing meanings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2b13e6ec-9e61-4436-8525-aa4c5f3bed8d
📒 Files selected for processing (21)
app/src/components/flows/canvas/nodeConfig/__tests__/memoryFields.test.tsxapp/src/components/flows/canvas/nodeConfig/memoryFields.tsxapp/src/components/flows/canvas/nodeConfig/nodeConfigFields.tsxapp/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsxapp/src/lib/flows/nodeKindMeta.tsapp/src/lib/flows/nodeSummary.tsapp/src/lib/flows/types.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.ts
| <NumberField | ||
| label={t('flows.nodeConfig.memory.limitLabel')} | ||
| hint={t('flows.nodeConfig.memory.limitHint')} | ||
| value={configNumber(config, 'limit')} | ||
| onChange={v => onChange({ limit: v })} | ||
| min={1} | ||
| step={1} | ||
| testId="node-config-memory-limit" | ||
| /> | ||
| <NumberField | ||
| label={t('flows.nodeConfig.memory.minScoreLabel')} | ||
| hint={t('flows.nodeConfig.memory.minScoreHint')} | ||
| value={configNumber(config, 'min_score')} | ||
| onChange={v => onChange({ min_score: v })} | ||
| min={0} | ||
| max={1} | ||
| step={0.05} | ||
| testId="node-config-memory-min-score" | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Enforce numeric bounds before persisting.
min/max/step do not block interactive changes, so this can save limit: 0, fractional limits, or min_score outside 0..1. Normalize or reject values in these callbacks and cover the boundaries.
Proposed fix
+const normalizeLimit = (value: number | undefined) =>
+ value === undefined || !Number.isFinite(value) ? undefined : Math.max(1, Math.floor(value));
+
+const normalizeMinScore = (value: number | undefined) =>
+ value === undefined || !Number.isFinite(value) ? undefined : Math.min(1, Math.max(0, value));
+
- onChange={v => onChange({ limit: v })}
+ onChange={v => onChange({ limit: normalizeLimit(v) })}
...
- onChange={v => onChange({ min_score: v })}
+ onChange={v => onChange({ min_score: normalizeMinScore(v) })}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <NumberField | |
| label={t('flows.nodeConfig.memory.limitLabel')} | |
| hint={t('flows.nodeConfig.memory.limitHint')} | |
| value={configNumber(config, 'limit')} | |
| onChange={v => onChange({ limit: v })} | |
| min={1} | |
| step={1} | |
| testId="node-config-memory-limit" | |
| /> | |
| <NumberField | |
| label={t('flows.nodeConfig.memory.minScoreLabel')} | |
| hint={t('flows.nodeConfig.memory.minScoreHint')} | |
| value={configNumber(config, 'min_score')} | |
| onChange={v => onChange({ min_score: v })} | |
| min={0} | |
| max={1} | |
| step={0.05} | |
| testId="node-config-memory-min-score" | |
| /> | |
| const normalizeLimit = (value: number | undefined) => | |
| value === undefined || !Number.isFinite(value) ? undefined : Math.max(1, Math.floor(value)); | |
| const normalizeMinScore = (value: number | undefined) => | |
| value === undefined || !Number.isFinite(value) ? undefined : Math.min(1, Math.max(0, value)); | |
| <NumberField | |
| label={t('flows.nodeConfig.memory.limitLabel')} | |
| hint={t('flows.nodeConfig.memory.limitHint')} | |
| value={configNumber(config, 'limit')} | |
| onChange={v => onChange({ limit: normalizeLimit(v) })} | |
| min={1} | |
| step={1} | |
| testId="node-config-memory-limit" | |
| /> | |
| <NumberField | |
| label={t('flows.nodeConfig.memory.minScoreLabel')} | |
| hint={t('flows.nodeConfig.memory.minScoreHint')} | |
| value={configNumber(config, 'min_score')} | |
| onChange={v => onChange({ min_score: normalizeMinScore(v) })} | |
| min={0} | |
| max={1} | |
| step={0.05} | |
| testId="node-config-memory-min-score" | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/components/flows/canvas/nodeConfig/memoryFields.tsx` around lines 194
- 212, Enforce valid numeric values in the onChange callbacks for the memory
limit and min_score NumberFields before calling onChange: normalize or reject
limit values below 1 or fractional values, and min_score values outside 0..1.
Preserve valid inputs and cover the boundary cases, rather than relying on the
visual min/max/step props.
| if (operation === 'flavour') { | ||
| const flavour = str(config, 'flavour'); | ||
| return flavour ? `Reads the "${flavour}" flavour` : 'Reads a memory flavour'; | ||
| } | ||
| if (operation === 'people') return 'Looks up people memory'; | ||
| if (operation === 'remember') return 'Remembers a value in this workflow'; | ||
| if (operation === 'forget') return 'Forgets a value from this workflow'; | ||
| // recall / search | ||
| const scoped = scope ? ` (${scope})` : ''; | ||
| return `Recalls memory${scoped}`; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Localize all new memory-node copy.
The new summaries and placeholders are English literals, so localized clients render untranslated memory UI.
app/src/lib/flows/nodeSummary.ts#L95-L104: resolve operation summaries through an i18n-aware rendering boundary or a passed translation function.app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L153-L153: replace the query placeholder with a translation key.app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L165-L165: replace the flavour placeholder with a translation key.app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L175-L175: replace the key placeholder with a translation key.app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L186-L186: replace the value placeholder with a translation key.
As per coding guidelines, “Use useT() from app/src/lib/i18n/I18nContext for all UI text,” and add new keys to every real locale file.
📍 Affects 2 files
app/src/lib/flows/nodeSummary.ts#L95-L104(this comment)app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L153-L153app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L165-L165app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L175-L175app/src/components/flows/canvas/nodeConfig/memoryFields.tsx#L186-L186
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/flows/nodeSummary.ts` around lines 95 - 104, Localize all new
memory-node UI text: update the operation-summary rendering around
nodeSummary.ts lines 95-104 to use an i18n-aware boundary or passed translation
function, and in memoryFields.tsx lines 153, 165, 175, and 186 replace the
query, flavour, key, and value placeholders with translation keys resolved via
useT() from I18nContext. Add the corresponding keys to every real locale file.
Source: Coding guidelines
| 'flows.nodeConfig.memory.queryLabel': 'Requête', | ||
| 'flows.nodeConfig.memory.queryOptionalHint': 'Optionnel: affine la recherche de personnes.', | ||
| 'flows.nodeConfig.memory.flavourLabel': 'Style', | ||
| 'flows.nodeConfig.memory.flavourHint': | ||
| 'Un ensemble de style ou de préférences à lire, par exemple email-tone.', |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the French wording and register.
"Optionnel: affine" mixes informal singular address with the surrounding formal vous style, and "un ensemble de style" should use the plural. Please update these hints for consistent, natural French.
Proposed wording
- 'flows.nodeConfig.memory.queryOptionalHint': 'Optionnel: affine la recherche de personnes.',
+ 'flows.nodeConfig.memory.queryOptionalHint': 'Facultatif : affinez la recherche de personnes.',
'flows.nodeConfig.memory.flavourLabel': 'Style',
'flows.nodeConfig.memory.flavourHint':
- 'Un ensemble de style ou de préférences à lire, par exemple email-tone.',
+ 'Un ensemble de styles ou de préférences à consulter, par exemple « email-tone ».',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'flows.nodeConfig.memory.queryLabel': 'Requête', | |
| 'flows.nodeConfig.memory.queryOptionalHint': 'Optionnel: affine la recherche de personnes.', | |
| 'flows.nodeConfig.memory.flavourLabel': 'Style', | |
| 'flows.nodeConfig.memory.flavourHint': | |
| 'Un ensemble de style ou de préférences à lire, par exemple email-tone.', | |
| 'flows.nodeConfig.memory.queryLabel': 'Requête', | |
| 'flows.nodeConfig.memory.queryOptionalHint': 'Facultatif : affinez la recherche de personnes.', | |
| 'flows.nodeConfig.memory.flavourLabel': 'Style', | |
| 'flows.nodeConfig.memory.flavourHint': | |
| 'Un ensemble de styles ou de préférences à consulter, par exemple « email-tone ».', |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/lib/i18n/fr.ts` around lines 4453 - 4457, Update the French hint
values for flows.nodeConfig.memory.queryOptionalHint and
flows.nodeConfig.memory.flavourHint to use consistent formal vous-register
wording and correct “style” to the plural “styles,” while preserving their
existing meanings.
…, min_score test) - flavour operation's config hint pointed authors at "email-tone", a slug the engine's memory_flavour reader rejects. Replace the free-text field with a dropdown of the seven valid persona facets so an invalid slug can't be typed, and fix the hint text; add the new i18n keys across all 14 locales. - search's node-card summary fell through to recall's "Recalls memory" wording. Give it its own "Searches memory (scope)" string. - add a min_score round-trip test symmetric with the existing limit test (emits a number, clears to undefined).
There was a problem hiding this comment.
♻️ Duplicate comments (1)
app/src/lib/flows/nodeSummary.ts (1)
92-106: 🎯 Functional Correctness | 🟠 MajorRoute memory summaries through i18n.
The new memory summaries hard-code English and print raw enum values such as
userandcoding_style, so the localized memory labels are never used by node cards. KeepdescribeNodepure by returning translation keys/structured data or accepting a translator at the React boundary; do not calluseT()inside this utility. This repeats the unresolved prior finding for Lines 95-105.As per coding guidelines, UI text in
app/srcmust useuseT()and new translation keys must be added to every real locale file.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/lib/flows/nodeSummary.ts` around lines 92 - 106, Update the memory branch in describeNode to stop returning hard-coded English and raw operation values; return translation keys/structured parameters or accept a translator supplied by the React boundary, without calling useT() inside this utility. Add the required memory summary keys to every real locale file and render them through useT() in the node-card UI.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@app/src/lib/flows/nodeSummary.ts`:
- Around line 92-106: Update the memory branch in describeNode to stop returning
hard-coded English and raw operation values; return translation keys/structured
parameters or accept a translator supplied by the React boundary, without
calling useT() inside this utility. Add the required memory summary keys to
every real locale file and render them through useT() in the node-card UI.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a5b25917-3927-4e1e-a357-96eb55d5ebb9
📒 Files selected for processing (18)
app/src/components/flows/canvas/nodeConfig/__tests__/memoryFields.test.tsxapp/src/components/flows/canvas/nodeConfig/memoryFields.tsxapp/src/lib/flows/nodeSummary.test.tsapp/src/lib/flows/nodeSummary.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.ts
🚧 Files skipped from review as they are similar to previous changes (15)
- app/src/lib/i18n/it.ts
- app/src/lib/i18n/fr.ts
- app/src/lib/i18n/de.ts
- app/src/lib/i18n/es.ts
- app/src/lib/i18n/hi.ts
- app/src/components/flows/canvas/nodeConfig/tests/memoryFields.test.tsx
- app/src/lib/i18n/zh-CN.ts
- app/src/lib/i18n/id.ts
- app/src/lib/i18n/pt.ts
- app/src/lib/i18n/pl.ts
- app/src/lib/i18n/ru.ts
- app/src/lib/i18n/en.ts
- app/src/lib/i18n/ar.ts
- app/src/lib/i18n/bn.ts
- app/src/components/flows/canvas/nodeConfig/memoryFields.tsx
Review comments addressed — pushed in
|
|
To use Codex here, create a Codex account and connect to github. |
|
All three fixes look solid:
One minor observation: the All three P2s are cleanly addressed. LGTM. Tip: You can customize Greptile's behavior for this repo with |
Part of the memory-in-workflows track (#5150 #5175 #5176 #5205). Frontend half of #5226. Pairs with the host adapter #5227 (and tinyhumansai/tinyflows#23). Merge after #5227 so users can't add a memory node before the backend provider is wired (an unwired node fails at runtime with a capability error).
What
Lets a user add and configure the new
memorynode on the Workflows canvas. Pure frontend (app/src/**), no Rust/submodule changes.lib/flows/types.ts—'memory'added toNodeKind(12→13).lib/flows/nodeKindMeta.ts—memoryentry: 🧠 icon,sagecolor,actionspalette group (sequenced 13th per08-memory-node.md).lib/flows/nodeSummary.ts— per-operation node-card summary line.components/flows/canvas/nodeConfig/memoryFields.tsx(new) —MemoryForm, wired intoNODE_CONFIG_FORMS.memory. Progressive disclosure: only the fields relevant to the selectedoperationrender. AddedNumberField/configNumberprimitives forlimit/min_score.flows.nodeKind.memory+ 23flows.nodeConfig.memory.*keys across all 14 locales, real translations, no em dashes.The security invariant, mirrored in the UI
When
operationisremember/forget, the scope control structurally omitsuser/flows(write ops only ever offerflow) — matching the engine's validate-time rejection. Two layers: the operation-switch handler clampsscopetoflow, and a mount/update effect self-heals any pre-existing invalid write+non-flow scope (e.g. from a raw-JSON edit or a builder proposal).Testing
memoryFields.test.tsx(10 tests): per-operation field visibility, the write→flow-only scope lock, the self-heal effect, numeric round-tripping. Verified:pnpm typecheckclean,i18n:check/i18n:english:check(0 missing/extra/leftover), no-em-dash check, eslint (incl.import/no-cycle) + prettier clean, 420/420 flows+i18n Vitest.Summary by CodeRabbit
New Features
Tests