cc1369a38b
Extend the rolling 7-day trial from disabled packs to missing (not-installed) ones: Workflow tab Missing rows get Install 7d (real install + trial) and Install (permanent). Expiry auto-disables (reuses processExpiredTrials, zero new expiry code). Install spec resolved from not-installed getlist entries; resulting dir discovered by re-reading getlist to key the trial. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
Design: Trial-install (7-day) for missing nodes on workflow load
Date: 2026-06-22 Status: Approved
Summary
Extend the existing 7-day rolling trial from disabled packages to missing (not-installed) ones. When a loaded workflow references node types whose owning pack isn't installed, the Workflow tab's Missing section gains:
- Install 7d — really install the pack via ComfyUI Manager, then register a
7-day trial. If it isn't used (executed) within 7 distinct boot-days, it
auto-disables (moves to
.disabled, reversible) via the existing expiry path. - Install — really install the pack permanently (no trial).
This makes trying out someone else's workflow non-committal: pull in what it
needs, and anything you don't actually use gets parked in .disabled instead of
permanently bloating the install (which slows boot — the whole point of this
extension).
Decisions (from clarification)
- Expiry action: auto-disable (not uninstall). This is free: the trial
table is keyed by package and
processExpiredTrials()already disables any expired trial pack and clears its row. No new expiry code. - Missing-row actions: both Install 7d and Install (permanent), symmetric with the Disabled section's Enable 7d / Enable. Both perform real installs from our UI; the current "open ComfyUI Manager" behavior becomes the failure fallback.
Current state it builds on (v1.6.0, verified)
tracker.py:trial_packagestable +start_trial/tick_boot_days/reset_trials_for/stop_trial/get_trials. Boot tick + usage reset + routes (/nodes-stats/trials[/start|/stop]) wired in__init__.py.js/nodes_stats.js:classifyUnresolved()(≈624) →{ disabled[], missing[] }; missing entries are{ type, pkg: <getmappings packKey> }where packKey is a dir name, registry id, or repo/gist URL.handleInstall(pkg, dialog)(≈1155) currently only opens Manager's Custom Nodes Manager.runManagerEnable(payload)(≈701) = reset → POST/manager/queue/install→ start →waitForQueue(install and enable share this endpoint; only the payload differs).processExpiredTrials()(≈1192) disables expired trial packs via Manager, thenstopTrial.fetchManagerInfo()(≈483) returns installed/disabled packs only — it skipsstate==="not-installed", so registry data for missing packs is not in hand and must be fetched separately.
Install resolution (the only real new logic)
Not-installed getlist entries expose enough to install (verified live):
- CNR pack:
id= cnr_id,version= semver,files=[git url]. - Git-only pack: no
id,version="unknown",files=[git url].
Plan:
resolveInstallTarget(packKey)— fetch/customnode/getlist?mode=local&skip_update=true, indexnot-installedentries by every identifier (key,id,files,repository) with the same normalize used inclassifyUnresolved(lowercase, strip trailing/,.git), and return the matching entry (or null).installPayload(entry)— mirror Manager's installNodes:{ id: entry.id || <key>, version: entry.version, files: entry.files, channel:"default", mode:"cache", selected_version: entry.version==="unknown" ? "unknown" : "latest", skip_post_install:false, ui_id:<key> }.- Install via the existing
runManagerEnable(payload)(same queue endpoint). findInstalledDir(entry)— re-fetch getlist; find the now-installed entry matchingentryby id/files/repo; its key is the directory name (needed to key the trial). Fallback: repo basename offiles[0](strip.git).- For Install 7d:
POST /nodes-stats/trials/start { package: <dir name> }. - Show the restart banner: "Installed X — restart to load it" (+ " for a 7-day trial").
Components (all in js/nodes_stats.js)
resolveInstallTarget(packKey),installPayload(entry),findInstalledDir(entry)— small, mostly-pure helpers.handleTrialInstall(pkg, dialog, temporary)— orchestrates resolve → install → dir discovery → (trial start if temporary) → restart banner; on any failure falls back to the existing open-Manager behavior ofhandleInstall.- Missing rows render
[Install 7d]+[Install]; wire them inwireWorkflowButtons. ReusesetWorkflowButtonsBusy,notify,showRestartBanner,managerIsBusy.
Data flow
Workflow tab (missing row)
Install 7d -> resolveInstallTarget(packKey) -> installPayload
-> runManagerEnable (POST install, start, wait)
-> findInstalledDir -> trials/start{dir}
-> restart banner "installed for 7-day trial"
Install -> same, minus trials/start
later boots -> tracker.tick_boot_days (distinct days)
use in prompt-> tracker.reset_trials_for (counter -> 0)
expiry -> processExpiredTrials (UI load) -> Manager disable + stopTrial
Error handling
- ComfyUI Manager absent → Missing actions inert (as today).
resolveInstallTargetmiss, install HTTP error, or git-url install blocked by Manager security level → toast + fall back tohandleInstall's open-Manager guidance. No crash.findInstalledDirreturns nothing (install didn't land) → don't register a trial; toast "installed; couldn't register trial — enable/disable manually".managerIsBusy()→ ask the user to retry (same guard as enable/disable).
Testing
- Backend unchanged → no new pytest; existing
tests/stays green. - Pure helpers (
resolveInstallTargetmatching,installPayload,findInstalledDirmatching) written standalone;node --checkfor syntax. - Manual: load a workflow needing a not-installed CNR pack → Install 7d installs
it,
/nodes-stats/trialsshows the resulting dir, restart banner appears; Install (permanent) installs without a trial row; a git-url-only/blocked pack falls back to opening Manager; Manager-absent path inert.
Files touched
js/nodes_stats.js— resolution helpers,handleTrialInstall, Missing-row buttons + wiring.README.md,pyproject.toml— docs + version bump (1.6.0 → 1.7.0).
Out of scope (YAGNI)
- Auto-uninstall on expiry (chose disable; reversible + free).
- Bulk "install all missing 7d" (start per-row; revisit if wanted).
- Replicating Manager's full dependency/security UX (fall back to Manager).