Skip to content

Feature/basic prompt drafts - #56

Open
Psylenceo wants to merge 7 commits into
ethanfel:mainfrom
Psylenceo:feature/basic-prompt-drafts
Open

Psylenceo wants to merge 7 commits into
ethanfel:mainfrom
Psylenceo:feature/basic-prompt-drafts

Conversation

@Psylenceo

Copy link
Copy Markdown

Adds an optional shot field, basic_prompt, for a plain-language scene idea
kept separate from the H3-formatted scene_prompt/prompt. It never affects
prompt_hash or generation by itself.

Visible and editable everywhere a scene's prompt already is — Plan (and
Modern), Studio Plan (even while prompt editing is delegated to an external
editor), Review Gate (gated by the existing prompt-editor setting), and both
Scene Prompt Editor nodes — kept in sync through the same companion-sync bus
already used for scene_prompt (rebaseScenePrompt, planHasNonPromptChanges,
and a new publishCompanionBasicPrompt/_h3PromptCompanionSetBasicPrompt
pair mirroring the existing prompt broadcast).

Only Rich Scene Prompt Editor's Optimize action turns a basic prompt into a
real scene_prompt; its history tree also shows/restores basic_prompt per
revision.

Bundled with this are three Review Gate routing fixes surfaced while testing
the above against nested-subgraph frontends:

  • deliverReview/routeReview's node-id lookups (findNodeByQualifiedId,
    upstreamPlanNode, prepareResume, reviewFallbackNode) now resolve
    against the true root graph instead of app.graph, which can itself be a
    subgraph's own graph object.
  • The fallback node picked by reviewFallbackNode() is now delivered with
    {verifyRun: false} — it was being re-run through the same strict
    run_name check that had already selected it as the lone remaining gate,
    which is what caused the Review Gate to hang with no video/prompt and
    unresponsive buttons.
  • Rejected review events now log which check failed and the expected/actual
    values, and a token's resolved routing decision is cached per-token so it
    isn't re-derived and re-warned about on every repeated message while a
    review stays open.

Commits

  • Add a per-scene basic prompt: draft, sync, optimize, and history
  • Fix Review Gate routing when app.graph is not the true root
  • Log why a review event was rejected, not just that it was
  • Fix Review Gate routing: fallback node was re-rejected by its own safety check
  • Stop re-logging the same review routing decision on every message

Adds an optional shot field, basic_prompt, for a plain-language scene
idea kept separate from the H3-formatted scene_prompt/prompt. It never
affects prompt_hash or generation by itself.

Visible and editable everywhere a scene's prompt already is - Plan
(and Modern), Studio Plan (even while prompt editing is delegated to
an external editor), Review Gate (gated by the existing prompt-editor
setting), and both Scene Prompt Editor nodes - kept in sync through
the same companion-sync bus already used for scene_prompt
(rebaseScenePrompt, planHasNonPromptChanges, and a new
publishCompanionBasicPrompt/_h3PromptCompanionSetBasicPrompt pair
mirroring the existing prompt broadcast).

Only Rich Scene Prompt Editor's Optimize action turns a basic prompt
into the real scene prompt: it now sends the basic prompt as content
alongside the existing Prompt Guide's style rule (the guide stays a
style axis, not content - confirmed it's just one rule sentence per
guide in richGuideInstruction). Every optimize run is saved as a
PromptHistoryStore revision carrying both the basic prompt and the H3
result (prompt_history.py's save_draft/mark_executed/_create gain an
optional basic_prompt parameter), and the existing history tree now
shows each revision's original basic prompt and restores both fields
together - the "go back and edit the original" requirement.

Review Gate's retry/reroll can also carry an edited basic prompt back
to the Plan (_plan_with_review_revision, _submit_review_decision), the
same way it already does for scene_prompt.
deliverReview/routeReview's node_id-based routing (findNodeByQualifiedId,
upstreamPlanNode, prepareResume, reviewFallbackNode) all traversed from
app.graph directly. app.graph can be a subgraph's own graph object
rather than the actual root (e.g. under nested-subgraph frontends), in
which case getNodeById(<top-level id>) and graph-wide node enumeration
both silently fail to find anything - the exact "Pending token ...
could not be routed to display node ..." console warning, followed by
the Review Gate node showing no video/prompt and its action buttons
finding "no pending review" since the live payload was never delivered.

h3_prompt_companion_sync.mjs already has the fix for this
(graphRoot(): node?.graph?.rootGraph ?? node?.graph); mirrored it here
as appRootGraph() and pointed every traversal-starting app.graph
reference at it. Left the few remaining app.graph uses alone (a local
graph reference for an undo transaction, and setDirtyCanvas calls),
since those operate on whichever graph is currently open by design.
deliverReview and the final routeReview failure both silently dropped
useful context: which check failed (wrong node type vs. run_name
mismatch), and what the expected/actual run_name values and upstream
Plan type actually were. Node lookup itself was confirmed working via
console (getNodeById resolves the node directly), so the remaining
rejection path has to be the type check or the run_name comparison in
deliverReview - this makes the next occurrence self-diagnosing instead
of requiring another round of manual console probing.
…ety check

Confirmed via the new diagnostic logging: node lookup by id worked
correctly (getNodeById resolved the exact node), but deliverReview
still rejected it because the Plan node's run_name widget ("h3_chain",
apparently reset/stale) no longer matched the backend's actual
run_name ("sammy_test") for the in-flight generation.

reviewFallbackNode() already exists specifically to handle this: it
tries an exact run_name match first, then an id-leaf match, and
finally explicitly trusts a lone remaining MiniMaxH3ChainReview node
when nothing else disambiguates it. But routeReview() then passed that
already-vetted fallback node back through deliverReview(), which
reapplied the exact same strict run_name check and rejected it again -
so a workflow with a single Review Gate node could never route a
review at all once its Plan's run_name widget diverged from what the
running generation actually used (e.g. after recreating a node, or any
other edit-while-a-generation-is-in-flight scenario).

deliverReview() now takes a verifyRun option; only the initial exact
qualified-id match uses it, and the fallback-resolved node is trusted
as-is, matching reviewFallbackNode's own resolution.
A pending token can arrive many times while a review stays open
(candidate/preview ticks, reconnect polling). Once a token has
successfully routed to a node - even via the run_name-mismatch
fallback path - remember that decision and reuse it silently instead
of repeating the exact-match attempt, mismatch warning, and fallback
notice on every subsequent message for the same token. Cleared when
the review resolves.
Psylenceo added a commit to Psylenceo/ComfyUI-MiniMaxH3-Context-Loop that referenced this pull request Sep 9, 2026
…atus

Both branches are now submitted upstream as PR ethanfel#55 and PR ethanfel#56.
@ethanfel

Copy link
Copy Markdown
Owner

Reviewed at a3d0cfe47db5fe9ec64d258bcd608182dc962150. The basic-draft feature and true-root graph traversal are useful, but I'm holding this PR for three reproducible issues:

1. Review Gate fallback can route a review into the wrong project

In routeReview, both the fallback and remembered-token paths call deliverReview(..., {verifyRun: false}). The fallback can choose a same-leaf-ID gate or the only gate even when its connected Plan explicitly belongs to a different run.

Reproduced with one gate, ID 22, connected to a Plan for project_B, then a non-durable review event {token: "test-token", node_id: 22, run_name: "project_A"}. Exact delivery rejects the run mismatch, but fallback delivers it anyway: the gate for B receives A's review token. That exposes another project's pending review controls in this workflow.

Please retain project/workflow identity validation during fallback. If stale widgets require recovery, resolve against the queued workflow/prompt identity rather than treating a singleton or leaf ID as sufficient proof. Also revalidate cached routing when the graph, connected Plan, or mounted node changes. Add regressions for different projects sharing a leaf ID and cached gates after workflow changes/removal.

2. Editing one prompt field can overwrite a newer edit to the other

rebaseScenePrompt now unconditionally copies both prompt and basic_prompt from the editor's local shot into the latest Plan.

Minimal reproduction with the real function:

const local = {shots: [{id: "one", prompt: "new H3 edit", basic_prompt: "old basic"}]};
const live = {shots: [{id: "one", prompt: "old H3", basic_prompt: "new basic from Studio"}]};
rebaseScenePrompt(local, live, 0);
// live.shots[0].basic_prompt is now "old basic".

An H3-only edit has discarded the newer basic draft. The reverse direction is also unsafe: a basic-only edit can copy stale H3 text. Please track/rebase the edited field independently, and ensure basic-draft synchronization is published/consumed by the ordinary prompt editors as well as Review Gate. Add concurrent-edit tests in both directions.

3. Saving a different basic draft mutates an already-executed history revision

save_draft finds an existing revision by H3 text alone, then overwrites its basic_prompt without checking whether that revision was executed. The matching path in mark_executed needs the same consideration.

Reproduced using PromptHistoryStore in a temporary directory:

old = store.mark_executed(
    "test_project", "scene_one", "same H3 result", "original basic draft"
)["revision"]
new = store.save_draft(
    "test_project", "scene_one", "same H3 result", old["id"], "different basic draft"
)["revision"]
# new["id"] == old["id"]
# Reading old["id"] now returns basic_prompt == "different basic draft".

This loses the original draft associated with an executed revision. Please include basic-draft content in revision matching or fork a child when it changes on an executed revision, while retaining mutable unexecuted drafts. Test different basic drafts producing the same H3 text, including re-execution.

The existing review, companion-sync, simple/rich editor, and prompt-history checks passed in the isolated PR worktree, but they do not cover these cases. Please add the regression tests with the fixes before we merge this one.

@Psylenceo

Copy link
Copy Markdown
Author

Yep.
Crap and there's also a major bug where all basic prompts can get deleted.

…ecuted drafts

Fixes three reproducible issues ethanfel found reviewing feature/basic-prompt-drafts
at a3d0cfe:

- Review Gate fallback could route a review to the wrong project's gate: a
  gate sharing a leaf id, or being the only gate in the graph, was treated as
  sufficient proof even when its connected Plan carried a different, known
  run_name. reviewFallbackNode now excludes any gate whose Plan is known to
  belong to a different run from leaf-id/singleton recovery. Cached routing
  decisions (routedReviewNodes) are also now forgotten when a gate node is
  removed, reconfigured (rewired to a different Plan), or the graph is
  reloaded, so a stale decision can never outlive the state it was based on.

- rebaseScenePrompt unconditionally copied both prompt and basic_prompt from
  the editor's local shot onto the live Plan, so an edit to one field could
  silently discard a newer, concurrent edit to the other (e.g. an H3-only
  edit erasing a basic draft just saved from Plan Studio, or vice versa).
  Added markShotFieldEdited() to track which field was actually edited at
  each input site in both Scene Prompt Editor nodes; rebaseScenePrompt now
  only carries forward tracked fields, leaving an untouched field as the
  live Plan has it. Untracked shots keep the previous copy-both behavior.

- PromptHistoryStore.save_draft/mark_executed matched an existing revision
  by H3 text alone, then overwrote its basic_prompt in place even when that
  revision had already been executed - silently losing the original draft
  attached to executed history. _find_prompt now prefers an exact
  (prompt, basic_prompt) match over the merely-most-recent prompt-only
  match; save_draft/mark_executed fork a new child revision when the basic
  draft differs on an already-executed revision, while unexecuted drafts
  remain mutable in place as before.

Also fixes a related bug found while testing basic_prompt end-to-end:
_effective_editor_plan's per-shot field whitelist never included
basic_prompt, so every reload of a saved/archived checkpoint's Plan JSON
silently wiped basic_prompt for every scene, including scenes never
executed yet. basic_prompt is now carried through like the other optional
shot fields.

Added regressions for: different projects sharing a leaf id and cached
gates surviving workflow changes; concurrent prompt/basic_prompt edits in
both directions; different basic drafts on the same H3 text against an
executed revision (including re-execution); and basic_prompt surviving
_effective_editor_plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Psylenceo

Copy link
Copy Markdown
Author

OK, i've pushed an update.
And resolved an issue where reloading a checkpoint wipes the basic prompts even though the checkpoint info still has the basic prompt.

ethanfel added a commit that referenced this pull request Sep 13, 2026
Adapt PR #56 basic-prompt authoring and checkpoint/history fixes from 4bceb18 and af95a6e. Exclude the PR review-routing changes. Preserve branch-specific behavior, reconcile basic drafts across linked editors, and test real write/broadcast paths, clears, debounce and retry persistence.

Co-authored-by: Psylenceo <Psylenceo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
ethanfel added a commit that referenced this pull request Sep 13, 2026
Adapt PR #56 basic-prompt authoring and checkpoint/history fixes from 4bceb18 and af95a6e. Exclude the PR review-routing changes. Preserve branch-specific behavior, reconcile basic drafts across linked editors, and test real write/broadcast paths, clears, debounce and retry persistence.

Co-authored-by: Psylenceo <Psylenceo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@ethanfel

Copy link
Copy Markdown
Owner

Thanks, the checkpoint-reload fix checks out. I ported the basic-prompt feature and your checkpoint/executed-history fixes to main (680c68d) and nightly (5d6b0f1), with contributor credit.

I also fixed the remaining editor-sync case: saving a basic draft in editor A, then editing the H3 prompt in editor B, could still restore the old basic draft. Both fields now synchronize before an editor marks the Plan current. Added tests using the real simple/rich editor write and receiver functions, including clears and delayed notifications.

The Review Gate routing changes are still excluded. At af95a6e, this still reproduces: route a token for project A, change the connected Plan's run name to project B without reloading the graph, then deliver the same token again. The cached path in routeReview still uses verifyRun: false, so B receives A's review. Clearing the cache on configure/remove does not cover an ordinary run-name change or live rewiring. Please validate the current project identity on cached delivery too, and add a behavioral regression for that sequence.

The targeted Python checks pass on both branches; the JavaScript suites pass 52 scripts on main and 66 on nightly. I have not merged or closed this PR because the routing issue remains.

@Psylenceo

Copy link
Copy Markdown
Author

ok, I'll shuffle that in with a different thought I had, to remove the basic prompt from teh studio node. Not sure if its really needed there so i'm thinking of removing it from that node.

also I'm tossing the idea around of using tabs instead of two separate prompt boxs stacked. Use a tab system so that the plan node, optimizer node, and review gate nodes don't need to be so tall.

@ethanfel

Copy link
Copy Markdown
Owner

About the red tags after Optimize: I reproduced the matching reference state at af95a6e using the actual reference resolver and optimizer-result handler in a small JavaScript test.

  • The optimized prompt is saved, but the editor redraws with the old reference state. A newly introduced @hero stays inactive with no <Picture 1> mapping, while its image source is still available for hover. Refreshing references against the new prompt fixes the mapping. Manually replacing a tag already performs that refresh, which explains your workaround. This part also reproduces on main and nightly without the basic-prompt feature: it is an existing rich-editor bug.
  • Separately, this PR prepares optimizer references from the shared/old H3 prompt, not basic_prompt. Tags present only in the basic prompt are therefore excluded from the Direct API optimizer's active media list.

The fix should include basic-prompt tags when preparing optimizer references, then refresh the reference state/tray before redrawing an applied result, covering Direct API, MCP, and pending-result application. A regression should add a tag through optimization without requiring a manual edit afterward.

This was not a full generation test; red tags alone do not prove the render omitted the references. No fix has been applied yet.

Also, a correction to my earlier status: the basic-prompt feature port has since been reverted from both main and nightly. This PR remains open for continued work.

… basic_prompt tags

Addresses ethanfel's follow-up review on PR ethanfel#56 at af95a6e:

- routeReview's remembered/cached delivery path called deliverReview with
  verifyRun: false unconditionally, unlike the exact-match path. A cached
  decision can outlive the identity it was cached for: a gate's connected
  Plan run_name (or Project Assets manager) can change live, with no graph
  reload, node removal, or reconfigure event to invalidate the cache via
  forgetRoutedNode. Reproduced: route a token for project A to a gate, change
  that gate's connected Plan to project B without reloading the graph, then
  deliver the same token again - B received A's review. The remembered path
  now defaults to verifyRun: true like the exact-match path, so a genuine
  identity change is caught and the stale cache entry is dropped in favor of
  fresh exact/fallback resolution, while a blank/stale run_name widget is
  still tolerated as before.

- The Rich Scene Prompt Editor's Optimize action built its reference-scanning
  prompt from the shared prompt and current H3 text only, never basic_prompt.
  A tag introduced only in the basic draft (the common case: describe the
  scene in plain language before ever touching H3 text) was reported
  "inactive" and its media excluded from optimizerResources(), so the Direct
  API optimizer never saw it. Reference scanning in optimizePrompt() now also
  scans basic_prompt.

- Applying an optimizer result (Direct API, MCP, or a previously pending
  result held back by a scene change) redrew the editor without refreshing
  reference records first, so a tag that Optimize just turned into real
  content stayed shown "inactive" until an unrelated manual edit happened to
  trigger a refresh. Added refreshTaggedReferencesForShot(), called from all
  three apply sites before redrawing.

Added regressions: cached routing must default to verifyRun: true (and must
not use verifyRun: false); optimizer reference scanning must include
basic_prompt; all three optimizer-apply sites must refresh tagged references
before redrawing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Psylenceo added a commit to Psylenceo/ComfyUI-MiniMaxH3-Context-Loop that referenced this pull request Sep 15, 2026
… basic_prompt tags

Addresses ethanfel's follow-up review on PR ethanfel#56 at af95a6e:

- routeReview's remembered/cached delivery path called deliverReview with
  verifyRun: false unconditionally, unlike the exact-match path. A cached
  decision can outlive the identity it was cached for: a gate's connected
  Plan run_name (or Project Assets manager) can change live, with no graph
  reload, node removal, or reconfigure event to invalidate the cache via
  forgetRoutedNode. Reproduced: route a token for project A to a gate, change
  that gate's connected Plan to project B without reloading the graph, then
  deliver the same token again - B received A's review. The remembered path
  now defaults to verifyRun: true like the exact-match path, so a genuine
  identity change is caught and the stale cache entry is dropped in favor of
  fresh exact/fallback resolution, while a blank/stale run_name widget is
  still tolerated as before.

- The Rich Scene Prompt Editor's Optimize action built its reference-scanning
  prompt from the shared prompt and current H3 text only, never basic_prompt.
  A tag introduced only in the basic draft (the common case: describe the
  scene in plain language before ever touching H3 text) was reported
  "inactive" and its media excluded from optimizerResources(), so the Direct
  API optimizer never saw it. Reference scanning in optimizePrompt() now also
  scans basic_prompt.

- Applying an optimizer result (Direct API, MCP, or a previously pending
  result held back by a scene change) redrew the editor without refreshing
  reference records first, so a tag that Optimize just turned into real
  content stayed shown "inactive" until an unrelated manual edit happened to
  trigger a refresh. Added refreshTaggedReferencesForShot(), called from all
  three apply sites before redrawing.

Added regressions: cached routing must default to verifyRun: true (and must
not use verifyRun: false); optimizer reference scanning must include
basic_prompt; all three optimizer-apply sites must refresh tagged references
before redrawing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ethanfel

Copy link
Copy Markdown
Owner

Thanks for the update. I retested 9e6a295: the cached Gate routing now correctly rejects a review after the connected project changes, including a run-name edit, a Project Assets change, and rewiring to another Plan. The checkpoint-reload and executed-history checks also pass. Basic-only tags now reach the optimizer's reference scan, and I verified that a connected tagged image is included in the Direct request.

There are still two issues to sort out before merging:

1. Editing through a second editor can still undo a saved basic draft

This is the remaining sync case from the earlier review, not the field-level rebase test:

  1. Connect two prompt editors to the same Plan, with basic_prompt = "old basic".
  2. Save "new basic from A" in editor A and let its companion notification reach B.
  3. Edit only the H3 prompt in editor B.
  4. The saved Plan now contains B's new H3 prompt, but basic_prompt has reverted to "old basic".

I reproduced this with rich → rich, simple → simple, rich → simple, and simple → rich, using the actual write and receiver functions.

The ordinary editors' flushPlanEffects() publishes the H3 prompt but not the basic draft. The receiving _h3PromptCompanionSetScenePrompt() updates only prompt, then marks state.lastValue current even though its local basic_prompt is stale. The next write skips rebasing and serializes that stale draft. See the rich-editor receiver; the simple editor has the same problem.

Please synchronize both fields before treating the local Plan as current, while preserving field-level edit ownership. The regression needs the full A-save → B-receive → B-save sequence, not just a direct call to rebaseScenePrompt. This one still causes data loss.

2. After Optimize, draft-only tags can change the displayed reference mapping

The new refreshTaggedReferencesForShot helper includes basic_prompt when rebuilding the applied H3 prompt's references.

Reproduction with two enabled carousel pictures, ordered @unused then @hero:

  • Basic draft: Consider @unused and @hero.
  • Optimizer result: A close-up of @hero.
  • Actual editor state: both references remain active; @hero maps to <Picture 2>.
  • Expected from the resulting H3 prompt: @unused is inactive; @hero maps to <Picture 1>.

This reproduces through Direct API, MCP, and pending-result application. Including basic-draft tags when preparing the optimizer request is correct; after applying the result, refresh against the shared prompt plus the resulting H3 text only. Please cover the case where a draft tag is omitted from the optimized result, as well as newly introduced tags.

This is a confirmed editor mapping mismatch, not a claim that a generated video used the wrong image. The tests were isolated JavaScript/backend checks, with no live generation or user-project changes. The existing targeted tests pass but miss these two sequences.

GitHub also currently reports merge conflicts, so the branch will need updating before it can merge.

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.

2 participants