<input type="file"> does nothing in a Craft window. Clicking one — or calling .click() on a hidden one, which is the common pattern behind an "Attach image" button — opens no picker, raises no error, and logs nothing to the console or to stderr. The page has no way to tell that it failed.
Cause
WKWebView does not open a file picker itself. It asks its WKUIDelegate for one, through:
- webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:If the delegate does not implement that method, WKWebView silently does nothing. Craft does not implement it. These are every webView: selector registered across packages/zig/src/:
webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:
webView:decidePolicyForNavigationAction:decisionHandler:
webView:decidePolicyForNavigationResponse:decisionHandler:
webView:didCommitNavigation:
webView:didReceiveAuthenticationChallenge:completionHandler:
webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:runOpenPanel... is absent, so every file input in every Craft window is inert.
Worth noting the media-capture delegate is right there at macos.zig:6546 — camera and microphone prompts work. File selection is the one input path that was not wired.
The pieces already exist
system.zig already drives NSOpenPanel for the scripting API:
system.zig:405—pub fn selectDirectory(options: FileDialog)system.zig:419—objc.objc_getClass("NSOpenPanel")
So this is not new platform work. It is adding the delegate method alongside the one at macos.zig:6546, opening a panel the way selectDirectory already does, and handing the chosen URLs back through the completion handler.
The parameters object carries what the panel should reflect: allowsMultipleSelection, and on recent macOS allowedFileExtensions / allowsDirectories. Honouring allowsMultipleSelection matters — a page that sets multiple and receives one file has no way to know why.
Calling the completion handler with nil when the user cancels is what lets the page's change event stay unfired, which is the behaviour a browser gives.
Reproducing
Any page with a file input, in any Craft window:
<input type="file" accept="image/*">craft https://example.test/with-a-file-input --titlebar-hiddenClick it. Nothing happens. Same result for the hidden-input pattern:
const input = document.querySelector('input[type=file]')
input.click() // no panel, no error, no rejected promiseObserved on craft 0.0.294, macOS, with a locally built zig-out/bin/craft.
Why it is worth fixing rather than documenting
An app shipping through the Mac App Store cannot ask its users to "attach an image in the browser version instead". And the failure mode is the bad kind: the markup is correct, it works when the same page is opened in Safari, and nothing anywhere says why it does not work here — so the time goes into the app's own code first. That is where I spent it before finding this.