Skip to content

Take images into the New Node dialog - #393

Open
scgopi wants to merge 2 commits into
mainfrom
feat/prompt-image-attachments
Open

scgopi wants to merge 2 commits into
mainfrom
feat/prompt-image-attachments

Conversation

@scgopi

@scgopi scgopi commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Pasting a screenshot into the New Node dialog did nothing at all. A SwiftUI TextField's field editor owns ⌘V and accepts only text, so the keystroke landed nowhere and the dialog read as broken.

What now happens

⌘V or a drag onto the brief field writes the image beside the node's memory (~/.graphcode/memory/PROJECT/NODE/attachments/) and puts [image #1] in the field. The picture itself can never travel — zmx starts a session by typing its launch command into a PTY, so a prompt is text on a canonical-mode line — so the placeholder is swapped for the file's path in LoopNode.sessionPrompt, which is the one place both launchers already read. A path is something every backend can open, and three of the five have a flag for it.

The draft's id is the node's id (NodeDraft.id), which is what lets the bytes be filed under their final home before the node exists.

Decision Why
Path at launch, not a follow-up message A follow-up races the TUI composer and does nothing for an unattended loop nobody opens.
Placeholder resolved into the human's own field /goal and /loop take the rest of the line, so a path appended past the predicate would become part of what an evaluator judges.
A board with words and pixels is read as words Copying out of a rich-text editor puts a rendering of the selection beside the text; eating that ⌘V would lose a paste the human meant. A copied image file is unambiguous and wins regardless.
Refused for a remote graph Its ensure dial delivers text, so a local path names a file that host has never seen. The field says so rather than handing the agent a missing file.
Refused for a composite It never opens a session, so it has no prompt for a path to ride in.
Attachments dropped on graph import The file lives under the exporting node's id, which the import remap changes. Same reasoning as the worktree binding's.

ProjectFeature.swift sat at swiftlint's file-length and type-body budgets, so the New Node dialog's own reducer code moves to ProjectFeature+NodeForm.swift unchanged — the change does not fit otherwise.

Evidence

Rebased onto e28a1499 and re-run from the worktree with a private DerivedData path.

RED: xcodebuild test -only-testing:graphcodeTests/PromptAttachmentTests with PromptAttachments.resolving short-circuited to return its input -> exit=65, TEST FAILED, 7 of the new tests fail

GREEN: xcodebuild test -only-testing on the three new suites with resolution restored -> exit=0, TEST SUCCEEDED, all 24 new tests pass and are confirmed individually by name in the log

REGRESSION: xcodebuild -scheme graphcode test, then -scheme graphcode-cli build and -scheme graphcoded build -> exit=0 each, 1920 passed / 0 failed / 0 skipped per the xcresult summary

The 7 that go red without resolution: aPlaceholderBecomesThePathWhereItStood, eachPlaceholderTakesItsOwnPath, animageTheTextNeverNamedIsStatedAtTheEnd, aPromptThatIsOnlyAnImageIsStillAPrompt, aGoalCarriesThePathInsideTheConditionRatherThanAfterIt, aSketchWhoseNoteIsOnlyAnImageOpensAnyway, aTimedLoopKeepsItsDirectiveAndCarriesThePathIntoTheTask.

Step Result
swiftlint lint 0 errors
swift format lint --recursive --strict 0 findings

The rebase took main's newer .goalBased branch (the report-done sentence) and its draftModelTier defaults, both kept.

Not in this cut

Remote graphs (would need base64 through the ensure script), and the native attach flags where a CLI supports them interactively (codex -i, pi with an at-prefixed file) — the path in the prompt already covers all five backends, so those are a refinement rather than a foundation. Copilot's --attachment is non-interactive only and cannot be used at all.

🤖 Generated with Claude Code

Gate re-run from the worktree on the rebased branch; the macOS job covers the same suites in CI.

scgopi and others added 2 commits September 20, 2026 11:45
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
Pasting a screenshot into the New Node dialog did nothing at all: a SwiftUI
`TextField`'s field editor owns ⌘V and can only accept text, so the keystroke
landed nowhere and the dialog read as broken.

⌘V and drag-and-drop now take an image, write it beside the node's memory, and put
`[image #1]` in the brief where the caret was heading. The picture itself cannot
travel — `zmx` starts a session by typing its launch command into a PTY — so the
placeholder is swapped for the file's path when the prompt is composed, which is
one place both launchers already read.

A board carrying words as well as pixels is read as words: copying out of a
rich-text editor puts a rendering of the selection beside the text, and eating that
paste would lose something the human meant to type. A copied image *file* is
unambiguous and wins regardless.

Not offered for a loop on another machine, whose ensure dial delivers text, or for
a composite, which never opens a session.

`ProjectFeature.swift` was at swiftlint's file-length and type-body budgets, so the
New Node dialog's own reducer code moves to `ProjectFeature+NodeForm.swift`
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi
scgopi force-pushed the feat/prompt-image-attachments branch from 0769bcb to abc7603 Compare September 20, 2026 18:49
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.

1 participant