Skip to content
Merged
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
77 changes: 77 additions & 0 deletions graphcode-windows/src/GraphContextMenu.zig
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,83 @@ test "sketch promotion real menu plan exposes only three eligible target actions
}
}

test "sketch promotion production popup installs native Goal Turn Timed submenu on hidden HWND" {
const hwnd = c.CreateWindowExW(
0,
std.unicode.utf8ToUtf16LeStringLiteral("STATIC"),
std.unicode.utf8ToUtf16LeStringLiteral("Promotion menu test"),
c.WS_OVERLAPPEDWINDOW,
0,
0,
0,
0,
null,
null,
c.GetModuleHandleW(null),
null,
) orelse return error.WindowCreationFailed;
defer _ = c.DestroyWindow(hwnd);

const context = SketchPromotion.Context{};
const cases = [_]struct { sketch: bool, context: ?*const SketchPromotion.Context, enabled: bool }{
.{ .sketch = false, .context = null, .enabled = false },
.{ .sketch = true, .context = null, .enabled = false },
.{ .sketch = true, .context = &context, .enabled = true },
};
for (cases) |case| {
const popup = buildMenu(.{ .node = .{
.project_path = "C:\\fixture",
.id = "sketch-a",
.sketch = case.sketch,
.promotion_context = case.context,
} }) orelse return error.MenuCreationFailed;
const bar = c.CreateMenu() orelse {
_ = c.DestroyMenu(popup);
return error.MenuCreationFailed;
};
defer {
_ = c.SetMenu(hwnd, null);
_ = c.DestroyMenu(bar);
}
if (c.AppendMenuW(bar, c.MF_POPUP | c.MF_STRING, @intFromPtr(popup), std.unicode.utf8ToUtf16LeStringLiteral("Node").ptr) == 0) {
_ = c.DestroyMenu(popup);
return error.MenuInstallFailed;
}
if (c.SetMenu(hwnd, bar) == 0) return error.MenuInstallFailed;
const menu = c.GetSubMenu(c.GetMenu(hwnd), 0);
try std.testing.expect(menu != null);
var edit_position: c_int = 0;
while (edit_position < c.GetMenuItemCount(menu) and c.GetMenuItemID(menu, edit_position) != ids.edit_node) : (edit_position += 1) {}
try std.testing.expect(edit_position < c.GetMenuItemCount(menu));
const submenu = c.GetSubMenu(menu, edit_position + 1);
if (!case.sketch) {
try std.testing.expect(submenu == null);
for ([_]c.UINT{ 5116, 5117, 5118 }) |id|
try std.testing.expectEqual(std.math.maxInt(c.UINT), c.GetMenuState(menu, id, c.MF_BYCOMMAND));
continue;
}
try std.testing.expect(submenu != null);
var title: [64]u16 = undefined;
const title_len = c.GetMenuStringW(menu, @intCast(edit_position + 1), &title, title.len, c.MF_BYPOSITION);
try std.testing.expectEqualSlices(u16, std.unicode.utf8ToUtf16LeStringLiteral("Promote to..."), title[0..@intCast(title_len)]);
try std.testing.expectEqual(@as(c_int, 3), c.GetMenuItemCount(submenu));
for ([_]struct { id: c.UINT, label: []const u16, action: Action }{
.{ .id = 5116, .label = std.unicode.utf8ToUtf16LeStringLiteral("Goal - asks for a done check"), .action = .promote_goal },
.{ .id = 5117, .label = std.unicode.utf8ToUtf16LeStringLiteral("Turn - asks where to pause"), .action = .promote_turn },
.{ .id = 5118, .label = std.unicode.utf8ToUtf16LeStringLiteral("Timed - asks for a cadence"), .action = .promote_timed },
}, 0..) |item, index| {
try std.testing.expectEqual(item.id, c.GetMenuItemID(submenu, @intCast(index)));
const state = c.GetMenuState(submenu, item.id, c.MF_BYCOMMAND);
try std.testing.expect(state != std.math.maxInt(c.UINT));
try std.testing.expectEqual(case.enabled, state & c.MF_GRAYED == 0);
var label: [64]u16 = undefined;
const length = c.GetMenuStringW(submenu, item.id, &label, label.len, c.MF_BYCOMMAND);
try std.testing.expectEqualSlices(u16, item.label, label[0..@intCast(length)]);
try std.testing.expectEqual(item.action, actionForCommand(@intCast(item.id)));
}
}
}

test "background menu exposes supported folder actions and gates edge creation" {
const menu = buildMenu(.{ .background = .{
.project_path = "C:\\fixture",
Expand Down
2 changes: 1 addition & 1 deletion investigation/ui-parity-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ native keyboard/accelerator/window proof; the workspace row remains Partial.
| Node update/rename | Dedicated rename prompt and safe typed updates | Rename retains its dedicated safe prompt. Edit Details uses `NativeForms.update` and `sendUpdateNodeForm`; its project/node identity is now owned across the modal message loop and re-resolved afterward, and changed strings are compared against the form's owned initial snapshot rather than potentially freed graph strings. Original numeric scalars and clear-versus-unchanged semantics are preserved. Production-helper tests cover source mutation, unchanged/changed typed fields, cancellation, clearing, and allocation failures. The lifetime test rejects the original borrowed baseline. Live attempts stopped at background-menu activation before reaching editor or rename actions, so app-level dispatch, cancellation, and returned model results remain unverified in this work | Partial |
| Delete confirmations | Named object, consequences, safe default | Loop deletion names the loop and explains graph-connection removal. Edge deletion now names both endpoint loops and the connection kind, explains that the loops remain, re-resolves the stable edge after confirmation, and defaults to cancellation | Validated |
| Canvas context menu | Folder actions on background; complete node/edge actions | Project backgrounds now expose existing Worktrees, Project Settings, and Explorer routes with captured project context; unavailable non-filesystem actions are disabled and global scope omits them. Create Edge remains present but disabled with fewer than two loops. Resolved node menus omit Stop, matching macOS; composite/template/unwired actions and the absence of Message/Memo are preserved. Tests inspect the same native HMENU builder used by `TrackPopupMenu`, including an exact disabled-item assertion that fails when Create Edge is removed. The existing gate already inspects plain/composite/unwired popup contents; its succeeded-node Stop assertion now requires absence. Focused live attempts read the real background popup but did not achieve an action/result, and a minimal native control failed foreground acquisition before opening its menu; the cause remains unproven. Live node/edge/background action results, New Child live proof, and import/export remain deferred; custody child creation and sketch promotion retain their separate Partial evidence rows | Partial |
| Sketch promotion | Promote a sketch to Goal, Turn, or Timed without replacing its identity or session | Canvas and sidebar context menus use one production submenu plan and focused native forms. Owned popup/form snapshots reject stale scope, selection, type, and deletion; initial selection of a different cached project remains supported independently of the observation subscription. Filtered pure tests exercise the actual App selection adapter, typed native-form builder, and unstarted DaemonClient outbound queue, including composite addressing and allocation failures. Three emitted fixtures decode as existing Swift `promoteNode` variants, and the full Windows application builds ReleaseSafe. This proves local command construction/queueing only: no native keyboard, HMENU/HWND, UIA, app launch, or real-daemon acceptance/persistence was exercised for this feature | Partial |
| Sketch promotion | Promote a sketch to Goal, Turn, or Timed without replacing its identity or session | Canvas and sidebar context menus use one production submenu plan and focused native forms. Owned popup/form snapshots reject stale scope, selection, type, and deletion; initial selection of a different cached project remains supported independently of the observation subscription. Filtered pure tests exercise the actual App selection adapter, typed native-form builder, and unstarted DaemonClient outbound queue, including composite addressing and allocation failures. Three emitted fixtures decode as existing Swift `promoteNode` variants, and the full Windows application builds ReleaseSafe. A native Win32 test attaches the production node popup to a hidden HWND and checks the Goal/Turn/Timed submenu's presence, IDs, labels, and enabled states through the actual HMENU; it does not show or select the popup. Local command construction/queueing and this native menu state are proven, but native keyboard interaction, UIA, app launch, and real-daemon acceptance/persistence remain unexercised | Partial |

## Quick Chats

Expand Down
Loading