From 8dea93a07d935b1c871076ad537e96497e8efd88 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 08:17:11 +0000 Subject: [PATCH 1/5] fix(app): use single-array spawnProcess form on POSIX The macOS/Linux branches of openWorkflowTemplatesStore() called spawnProcess("open"/"xdg-open", [url]), but std.process has no (program, args) overload, so the app failed to compile on POSIX (Windows CI never compiled these branches). Pass a single args array. Co-authored-by: Ryan Johnson --- app/src/modules/workflow_templates_store/browser.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/modules/workflow_templates_store/browser.d b/app/src/modules/workflow_templates_store/browser.d index e75b515..0b5a834 100644 --- a/app/src/modules/workflow_templates_store/browser.d +++ b/app/src/modules/workflow_templates_store/browser.d @@ -46,9 +46,9 @@ bool openWorkflowTemplatesStore() else version (Posix) { version (OSX) - return spawnProcess("open", [url]).wait() == 0; + return spawnProcess(["open", url]).wait() == 0; else - return spawnProcess("xdg-open", [url]).wait() == 0; + return spawnProcess(["xdg-open", url]).wait() == 0; } else return false; From bc49c97cd8f22bcc656bc6e6550b408701d808f1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 08:17:11 +0000 Subject: [PATCH 2/5] fix(app): bound host-context detection probes with a timeout detectHostContext() runs each context's detect command synchronously on the UI thread at startup. A probe like 'npx convex --version' triggers an on-demand package fetch that never returns, hanging the app before its window opens. Run each probe with a null stdin and a 4s timeout, killing it on expiry, so a slow/hanging probe can no longer block startup. Co-authored-by: Ryan Johnson --- app/src/modules/cli_tools/context.d | 50 +++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/app/src/modules/cli_tools/context.d b/app/src/modules/cli_tools/context.d index 0cd5a42..903642a 100644 --- a/app/src/modules/cli_tools/context.d +++ b/app/src/modules/cli_tools/context.d @@ -1,17 +1,53 @@ module modules.cli_tools.context; import modules.cli_tools.model; -import std.process : execute; +import std.process : spawnProcess, tryWait, kill, wait; +import std.stdio : File; import std.algorithm : canFind; +import core.thread : Thread; +import core.time : msecs, Duration; +import std.datetime.stopwatch : StopWatch, AutoStart; + +version (Windows) + private enum nullDevice = "NUL"; +else + private enum nullDevice = "/dev/null"; + +/// Time budget for a single host-context probe. Probes run on the UI thread at +/// startup, so a command that never returns (e.g. `npx ` fetching an +/// uninstalled package) must not be allowed to block the app indefinitely. +private enum Duration detectTimeout = 4000.msecs; private bool detectCommandWorks(string detectCmd) { if (detectCmd.length == 0) return false; - version (Windows) { - auto r = execute(["cmd", "/c", detectCmd ~ " >nul 2>&1"]); - return r.status == 0; - } else { - auto r = execute(["sh", "-c", detectCmd ~ " >/dev/null 2>&1"]); - return r.status == 0; + + string[] args; + version (Windows) + args = ["cmd", "/c", detectCmd]; + else + args = ["sh", "-c", detectCmd]; + + try { + // Null stdin keeps interactive prompts (e.g. npx's install confirmation) + // from stalling the probe; null stdout/stderr discards probe output. + auto devNullIn = File(nullDevice, "r"); + auto devNullOut = File(nullDevice, "w"); + auto pid = spawnProcess(args, devNullIn, devNullOut, devNullOut); + + auto sw = StopWatch(AutoStart.yes); + while (true) { + auto res = tryWait(pid); + if (res.terminated) + return res.status == 0; + if (sw.peek() > detectTimeout) { + kill(pid); + wait(pid); + return false; + } + Thread.sleep(50.msecs); + } + } catch (Exception) { + return false; } } From 21109e74620d4c794267064c2eab90d2cdb47ff7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 08:17:11 +0000 Subject: [PATCH 3/5] fix(app): guard ToolchainAdvisorPanel against null context panel DecisionStepBox fires its onChange callback from its constructor (selectByIndex), which calls refreshContext() before _contextPanel is created, causing a null-dereference segfault on startup. Skip the update until the panel exists; the explicit refreshContext() after construction still performs the real initial update. Co-authored-by: Ryan Johnson --- app/src/modules/toolchain_advisor/ui.d | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/modules/toolchain_advisor/ui.d b/app/src/modules/toolchain_advisor/ui.d index 3581425..b748bd2 100644 --- a/app/src/modules/toolchain_advisor/ui.d +++ b/app/src/modules/toolchain_advisor/ui.d @@ -397,6 +397,12 @@ class ToolchainAdvisorPanel : VerticalLayout private void refreshContext() { + // DecisionStepBox fires its change callback while it is being + // constructed (selectByIndex in its ctor), which reaches here before + // _contextPanel is created. Skip until the panel exists; the explicit + // refreshContext() after construction performs the real initial update. + if (_contextPanel is null) + return; _contextPanel.update(_focusStepId, _selections); } } From fcfc291746342b4489ecccaf88f8ed604a6227fa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 08:17:20 +0000 Subject: [PATCH 4/5] chore(docs): add @antora/lunr-extension dev dependency antora-playbook.yml requires the @antora/lunr-extension, but it was not declared in package.json, so 'pnpm install' + 'antora antora-playbook.yml' failed locally with MODULE_NOT_FOUND. Add it as a devDependency so the docs site builds from a plain checkout. Co-authored-by: Ryan Johnson --- package.json | 1 + pnpm-lock.yaml | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/package.json b/package.json index cb4c619..09a6241 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "devcentr", "private": true, "devDependencies": { + "@antora/lunr-extension": "1.0.0-alpha.13", "antora": "^3.1.14" }, "packageManager": "pnpm@9.15.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b80a58..d3dfaf5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@antora/lunr-extension': + specifier: 1.0.0-alpha.13 + version: 1.0.0-alpha.13 antora: specifier: ^3.1.14 version: 3.1.14 @@ -47,6 +50,10 @@ packages: resolution: {integrity: sha512-kVEeGqZbXR903hPIm+BlN97fLdQ3LoUzE/BOPZ6vRp9m9Mmbnm67Kg7fSYkfTMLB0S2UWpAPFg22RdsU5ZoAzA==} engines: {node: '>=16.0.0'} + '@antora/lunr-extension@1.0.0-alpha.13': + resolution: {integrity: sha512-u8n8XLB6elMmXbW0bdeL5jG8UBJi6PSiz1zaMn+wIIIu/bnxotRBW4kEWSge+zTfdF4rEYMcJ9LvkAOamMyuKQ==} + engines: {node: '>=16.0.0'} + '@antora/navigation-builder@3.1.14': resolution: {integrity: sha512-/637YLGD7oUHGSfEfszXkk4ASfIhDAg5Xs9035J1dV07XYRlGqmtUb15rtapbcECpcQFjCyM5jFQYSNNvLrGcQ==} engines: {node: '>=16.0.0'} @@ -220,6 +227,19 @@ packages: diff3@0.0.3: resolution: {integrity: sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -227,6 +247,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -331,6 +355,9 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -389,6 +416,12 @@ packages: lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lunr-languages@1.10.0: + resolution: {integrity: sha512-BBjKKcwrieJlzwwc9M5H/MRXGJ2qyOSDx/NXYiwkuKjiLOOoouh0WsDzeqcLoUWcX31y7i8sb8IgsZKObdUCkw==} + + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -707,6 +740,12 @@ snapshots: pino-pretty: 11.2.2 sonic-boom: 4.0.1 + '@antora/lunr-extension@1.0.0-alpha.13': + dependencies: + htmlparser2: 9.1.0 + lunr: 2.3.9 + lunr-languages: 1.10.0 + '@antora/navigation-builder@3.1.14': dependencies: '@antora/asciidoc-loader': 3.1.14 @@ -909,6 +948,24 @@ snapshots: diff3@0.0.3: {} + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -919,6 +976,8 @@ snapshots: dependencies: once: 1.4.0 + entities@4.5.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -1029,6 +1088,13 @@ snapshots: hpagent@1.2.0: {} + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -1080,6 +1146,10 @@ snapshots: lodash.clonedeep@4.5.0: {} + lunr-languages@1.10.0: {} + + lunr@2.3.9: {} + math-intrinsics@1.1.0: {} merge2@1.4.1: {} From 9185151a8a8a84bdef5ec026de70f69a94b39a2e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 08:17:20 +0000 Subject: [PATCH 5/5] chore: add Cursor cloud dev environment config Add .cursor/environment.json + install.sh to provision the toolchain the DevCentr monorepo needs: system libs for dlangui (OpenGL/SDL2/FreeType/ X11), the LDC D compiler + dub (matching CI's ldc-latest), and the pnpm deps for the app catalog and Antora docs, then builds the app to verify. Also ignore the extensionless Linux build artifact app/DevCentr. Co-authored-by: Ryan Johnson --- .cursor/environment.json | 4 ++++ .cursor/install.sh | 44 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 3 files changed, 49 insertions(+) create mode 100644 .cursor/environment.json create mode 100755 .cursor/install.sh diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 0000000..a13c166 --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,4 @@ +{ + "name": "DevCentr", + "install": "bash .cursor/install.sh" +} diff --git a/.cursor/install.sh b/.cursor/install.sh new file mode 100755 index 0000000..2d2bce6 --- /dev/null +++ b/.cursor/install.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Cloud Agent bootstrap for the DevCentr monorepo. +# +# Installs everything needed to build and run the D desktop app (under app/) +# and to build the Antora docs (under docs/). Written to be idempotent so it +# can safely run against a cached or partially prepared environment. +set -euo pipefail + +LDC_VERSION="ldc-1.42.0" +DLANG_DIR="${HOME}/dlang" +LDC_BIN="${DLANG_DIR}/${LDC_VERSION}/bin" + +echo "==> Installing system libraries (build tools + dlangui runtime deps)" +# dlangui renders through OpenGL/SDL2 and loads fonts via FreeType, so the app +# needs the OpenGL, SDL2, FreeType, and X11 development libraries to build/run. +sudo apt-get update -qq +sudo apt-get install -y --no-install-recommends \ + ca-certificates curl xz-utils git build-essential pkg-config \ + libgl1-mesa-dev libglu1-mesa-dev libsdl2-dev libfreetype6-dev \ + libx11-dev libxext-dev libxcursor-dev libxinerama-dev libxi-dev \ + libxrandr-dev libxss-dev libdbus-1-dev \ + fonts-dejavu-core fonts-liberation + +echo "==> Installing D toolchain (${LDC_VERSION}) + dub" +# Matches CI's ldc-latest line (release.yml). LDC ships dub, so no separate dub. +if [ ! -x "${LDC_BIN}/ldc2" ]; then + curl -fsSL https://dlang.org/install.sh -o /tmp/install-d.sh + bash /tmp/install-d.sh "${LDC_VERSION}" +fi +# Put ldc2/ldmd2/dub on the default PATH so no `source activate` is needed. +# LDC resolves its ldc2.conf via the binary's real path, so symlinks are safe. +for tool in ldc2 ldmd2 dub; do + sudo ln -sf "${LDC_BIN}/${tool}" "/usr/local/bin/${tool}" +done + +echo "==> Installing Node dependencies (docs + app catalog)" +corepack enable >/dev/null 2>&1 || true +pnpm install --frozen-lockfile +(cd app && pnpm install --frozen-lockfile) + +echo "==> Building the DevCentr app (verifies the toolchain end-to-end)" +(cd app && dub build --build=debug) + +echo "==> Setup complete: 'cd app && dub build' to build, run ./app/DevCentr under a display." diff --git a/.gitignore b/.gitignore index f7a4372..6fba3b9 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ app/build_output.txt app/*.exe app/*.pdb app/*.dll +app/DevCentr app/dub.selections.json # Dev Center cache and temporary files