Skip to content

feat(ui): fold hidden prompt sections in chat history#407

Open
pascalandr wants to merge 3 commits into
NeuralNomadsAI:devfrom
Pagecran:feat/hidden-prompt-sections
Open

feat(ui): fold hidden prompt sections in chat history#407
pascalandr wants to merge 3 commits into
NeuralNomadsAI:devfrom
Pagecran:feat/hidden-prompt-sections

Conversation

@pascalandr
Copy link
Copy Markdown
Contributor

@pascalandr pascalandr commented May 8, 2026

Summary

  • Add CodeNomad-specific hidden prompt markers for long user prompt sections.
  • Strip hidden markers before prompt submission while keeping those sections collapsed in chat history.
  • Persist the display-only folding metadata across optimistic message replacement and full app reloads.

Fixes #266

Verification

  • npx tsx --test "packages/ui/src/lib/hidden-prompt-sections.test.ts" "packages/ui/src/stores/message-prompt-display.test.ts"
  • npm run typecheck --workspace @codenomad/ui
  • npm run build --workspace @codenomad/ui

Possible follow-ups :

  • a small prompt-overlay hint showing <codenomad:hide>...</codenomad:hide>
  • an "Insert hidden section" action/button
  • a short help entry for advanced prompt formatting

pascalandr added 2 commits May 8, 2026 19:02
Add a CodeNomad-specific hidden prompt syntax for user messages so long planning and instruction blocks can stay out of the main chat flow without changing the text sent to the model.

User messages now strip `<codenomad:hide>` markers before prompt submission, render hidden sections inside collapsed details blocks in the message history, and persist that display-only structure across optimistic message replacement and full app reloads.

Validation:
- npx tsx --test "packages/ui/src/lib/hidden-prompt-sections.test.ts" "packages/ui/src/stores/message-prompt-display.test.ts"
- npm run typecheck --workspace @codenomad/ui
- npm run build --workspace @codenomad/ui
@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25568593846

Artifacts expire in 7 days.
Artifacts: (none found on this run)

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 8, 2026

Understood request: perform gatekeeper checks for the latest PR state, looking for regressions and better implementations. Assumption: this is a review-only pass, not a request to patch the branch.

Gatekeeper result: changes requested.

Findings:

  • Medium: packages/ui/src/stores/message-v2/instance-store.ts:1052 removes messages in pruneMessagesAfterRevert, but it never clears the corresponding hidden-prompt display overrides. This PR introduces localStorage persistence for display text, and most deletion paths were updated (removeMessage, clearSession, clearInstance), but the revert/prune path leaves deleted hidden prompt content behind. That is both stale metadata and a privacy/storage leak. Fix by clearing each removed message override inside pruneMessagesAfterRevert, or by introducing a shared helper for “remove these message IDs” so all deletion paths clean the new side store consistently.

  • Medium: packages/ui/src/stores/message-prompt-display.ts:1 persists the full original prompt text in localStorage, not just display metadata. The PR summary says “Persist the display-only folding metadata,” but the implementation duplicates the entire prompt, including hidden sections, in a browser store with no size bound or TTL. This is fragile for large prompts and creates another long-lived copy of potentially sensitive prompt content. A better implementation is to persist marker/range metadata keyed by message ID and render against the current message text from the message store, rather than storing a second full prompt body.

Additional quality notes:

  • packages/ui/src/components/message-item.tsx:600 has JSX indentation drift in the changed block. Not behavioral, but it is a code-quality smell in a touched file.

  • packages/ui/src/lib/hidden-prompt-sections.ts:27 strips marker tokens even when the hide section is malformed/unbalanced, while display splitting falls back to showing the marker text. That mismatch is probably acceptable if malformed markers are unsupported, but it should be an explicit choice and ideally covered by a test for preparePromptDisplayText.

Verification performed:

  • Passed: npx tsx --test "packages/ui/src/lib/hidden-prompt-sections.test.ts" "packages/ui/src/stores/message-prompt-display.test.ts"

  • Could not validate in this checkout: npm run typecheck --workspace @codenomad/ui fails broadly because dependencies/types are missing (solid-js, node:test, SDK packages, etc.). This does not look isolated to this PR’s code.

  • Could not validate in this checkout: npm run build --workspace @codenomad/ui fails because vite is missing.

Oversized touched files to keep on the refactor radar:

  • packages/ui/src/stores/message-v2/instance-store.ts is about 1,281 lines.

  • packages/ui/src/components/message-item.tsx is about 702 lines.

Recommendation: do not merge until the stale override cleanup is fixed and the persistence strategy is either narrowed to metadata/ranges or explicitly accepted as storing full prompt bodies in localStorage.

--
Yours,
CodeNomadBot

Use segment-length metadata instead of persisting the full original prompt body in localStorage so hidden prompt sections keep their collapsed display state without creating an extra long-lived copy of the prompt text.

This also clears the persisted metadata when revert pruning removes user messages, keeping the side store aligned with message deletion paths and avoiding stale hidden-section leftovers.

Validation:
- npx tsx --test "packages/ui/src/lib/hidden-prompt-sections.test.ts" "packages/ui/src/stores/message-prompt-display.test.ts"
- npm run typecheck --workspace @codenomad/ui
- npm run build --workspace @codenomad/ui
@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

Same review again please

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 8, 2026

Understood request: repeat the gatekeeper review for the latest PR state. Assumption: review-only pass, no branch changes requested.

Gatekeeper result: no blocking findings found in the latest state.

What changed since the prior review:

  • The stale override cleanup issue is addressed: pruneMessagesAfterRevert now clears prompt display overrides for pruned message IDs.

  • The full-prompt localStorage concern is addressed: persistence now stores segment metadata (hidden + length) instead of duplicating the original prompt body.

Review notes:

  • The metadata approach is a much better implementation for privacy and storage footprint. splitHiddenPromptSections also fails closed by returning null if persisted metadata no longer matches the message text length, which avoids rendering incorrect folds.

  • Malformed markers now stay as plain text for both send and display paths, and that behavior is covered by a test.

  • The JSX indentation drift from the previous pass was fixed.

Residual risks / suggestions, non-blocking:

  • resolveClientPromptDisplayText in packages/ui/src/stores/message-v2/instance-store.ts now resolves metadata, not text. Renaming it to resolveClientPromptDisplayMetadata would avoid future confusion, but this is not a merge blocker.

  • The metadata persistence is intentionally keyed by message ID in localStorage. That is acceptable for this feature, but if message IDs ever become reusable across imported/restored sessions, the text-length mismatch guard is the main protection.

Verification performed:

  • Passed: npx tsx --test "packages/ui/src/lib/hidden-prompt-sections.test.ts" "packages/ui/src/stores/message-prompt-display.test.ts"

  • Passed: git diff --check origin/dev...HEAD

  • Could not validate in this checkout: npm run typecheck --workspace @codenomad/ui still fails broadly because dependencies/types are missing (solid-js, node:test, SDK packages, etc.). This does not appear isolated to this PR.

  • Could not validate in this checkout: npm run build --workspace @codenomad/ui fails because vite is missing.

Oversized touched files to keep on the refactor radar:

  • packages/ui/src/stores/message-v2/instance-store.ts is about 1,283 lines.

  • packages/ui/src/components/message-item.tsx is about 707 lines.

Recommendation: acceptable to merge once CI confirms typecheck/build in a dependency-complete environment.

--
Yours,
CodeNomadBot

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented May 8, 2026

@pascalandr Thanks for working on this, but not sure we should do it. It might confuse users.

@pascalandr
Copy link
Copy Markdown
Contributor Author

@pascalandr Thanks for working on this, but not sure we should do it. It might confuse users.

I'm not sure how it can confuse users, what do you mean ?

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented May 9, 2026

@pascalandr Thanks for working on this, but not sure we should do it. It might confuse users.

I'm not sure how it can confuse users, what do you mean ?

I see that your are using codenomad:hide which is very explicit. My take is

  1. I don't see this feature being used by more than 1 user.
  2. Sending Intro<codenomad:hide>Secret</codenomad:hide>Outro will surely confuse models about what is sent
  3. If the user is sending secrets to model, its already in plain text everywhere.

@pascalandr
Copy link
Copy Markdown
Contributor Author

Oh I see but this is not the use case here : #266 (comment)
My wording was bad, I should have used codenomad:collapse instead , this is not about secrets but about convenience. This could be very useful when pasting something in your prompt but you want it to stay collapsed for the user for convenience but still not hidden to the model.
but I realize indeed this is exactly how I would like pasted text in my prompt to appear in the message window collapsed by default (like collapsed tool for example) so my implementation is bad at the moment, What if pasted text instead of converted to raw text in bulk could keep its form and be collapsed by defaut (and uncollapsed if needed) if it's possible, does it make sens ?

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented May 9, 2026

If we can do something without sending additional stuff to LLM then it could used.
For paste maybe, we can surround with with codeblocks with language paste and detect it automatically and collapse it. Models do understand markdown syntax and could work.

Maybe if needed, a codeblock with language hidden would be a better way, that way model would understand the codeblock and we can treat this codeblock specially and hide it.

TBH, I see this as low value vs high effort and maintenance and not very keen on it. It all adds up in the markdown rendering logic which is a heavy computation part already.

@pascalandr
Copy link
Copy Markdown
Contributor Author

If we can do something without sending additional stuff to LLM then it could used. For paste maybe, we can surround with with codeblocks with language paste and detect it automatically and collapse it. Models do understand markdown syntax and could work.

Maybe if needed, a codeblock with language hidden would be a better way, that way model would understand the codeblock and we can treat this codeblock specially and hide it.

TBH, I see this as low value vs high effort and maintenance and not very keen on it. It all adds up in the markdown rendering logic which is a heavy computation part already.

ok I'll keep this in mind and will go on only if the trade-off is worthwhile enough

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] A way to hide portions of a prompt in codenomad display

2 participants