Files
Comfyui-Nodes-Stats/docs/plans/2026-06-21-trial-enable-design.md
Ethanfel 1a2bd2bcef docs: design for workflow tab + temporary trial-enable
Approved brainstorming design: a Workflow tab that splits a loaded
workflow's unresolved nodes into Missing (defer to Manager) and Disabled
(enable temporarily under a 7 distinct-boot-day rolling trial, or
permanently). Backend tracks trial state and counts boot-days; frontend
drives Manager enable/disable (Approach A).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 11:58:45 +02:00

150 lines
7.0 KiB
Markdown

# Design: Workflow tab + temporary trial-enable for disabled packages
Date: 2026-06-21
Status: Approved
## Summary
When a loaded workflow references node types that aren't currently resolvable,
Node Stats shows a **Workflow** tab that splits them into:
- **Missing** — the owning package is not installed (or unknown). Handled by
ComfyUI Manager as usual (install).
- **Disabled** — the owning package is installed but disabled (in
`custom_nodes/.disabled`). Offers two actions:
- **Enable temporarily** — re-enable the package under a rolling 7-day trial.
- **Enable permanently** — normal re-enable, never auto-disabled.
A temporarily-enabled package auto-disables again if it goes **7 distinct
boot-days without being used** in an executed prompt. Any execution use resets
the counter back to 7 (rolling window), so a package stays enabled as long as it
keeps getting used.
## Decisions (from brainstorming)
- **Graduation model:** rolling. Each execution use resets the counter to the
full budget (7). 7 distinct boot-days unused → auto-disable. No permanent
"graduation"; a trial package stays trial-managed, kept alive by use.
- **"Used" means:** the package's node appears in an *executed/queued* prompt.
Reuses the existing usage tracking. Loading a workflow does not count.
- **Granularity:** the tab lists individual node types ("list by node"), but
every action resolves to and operates on the owning *package* (Manager works
per package). Trial state is tracked per package.
- **Surfacing:** on workflow load, if any node is disabled/missing, auto-open
the Node Stats dialog to the Workflow tab.
- **Disabled actions:** both "Enable temporarily" (trial) and "Enable
permanently".
- **Missing:** defer to ComfyUI Manager (install), as usual.
- **Orchestration (Approach A):** the Python backend only tracks trial state,
counts boot-days, resets on use, and flags expiry. The frontend performs all
ComfyUI-Manager enable/disable mutations (server is up, reuses the proven
disable code, no coupling to Manager internals). Day-counting stays accurate
across headless restarts; the actual auto-disable executes the next time the
web UI loads.
## Data model
New SQLite table `trial_packages` in the existing DB:
| column | meaning |
|---|---|
| `package` (PK) | directory name (matches our package keys) |
| `enabled_at` | ISO timestamp the trial started |
| `last_use_day` | `YYYY-MM-DD` of last execution use (init = enable day) |
| `last_boot_day` | `YYYY-MM-DD` of last boot-day counted |
| `unused_boot_days` | counter, 0..budget |
| `budget` | distinct boot-days allowed unused (default 7) |
### Lifecycle
- **Start trial** (after frontend temp-enable): upsert with
`unused_boot_days=0`, `last_boot_day=today`, `last_use_day=today`,
`enabled_at=now`. The enable day never counts toward expiry ("not the same
day").
- **Boot tick** (`tracker.tick_boot_days()`, called once at `__init__` import):
for each trial row, if `last_boot_day != today`: `unused_boot_days += 1`,
`last_boot_day = today`.
- **Expiry:** computed, not stored — `expired = unused_boot_days >= budget`.
- **Use reset** (`reset_trials_for(packages)`, called from `_record_prompt`
after `record_usage`): for each used package on trial, set
`unused_boot_days = 0`, `last_use_day = today`.
- **Stop trial** (`stop_trial(package)`): delete the row. Called after permanent
enable, after the frontend disables an expired pack, or if re-disabled.
Disabling an already-disabled package via Manager is a no-op, so a stale trial
row (e.g. package disabled out-of-band) resolves cleanly on next expiry pass.
## Backend (Python)
- `tracker.py`: add table to `SCHEMA`; methods `start_trial`, `stop_trial`,
`tick_boot_days`, `reset_trials_for(packages)`, `get_trials`.
- `__init__.py`:
- At import: `tracker.tick_boot_days()` wrapped in try/except (never blocks
extension load).
- In `_record_prompt`: after `record_usage`, map used class_types → packages
and call `tracker.reset_trials_for(packages)`.
- Routes:
- `GET /nodes-stats/trials` → `[{package, unused_boot_days, budget,
days_remaining, expired, enabled_at, last_use_day}, ...]`
- `POST /nodes-stats/trials/start` `{package}` → upsert trial
- `POST /nodes-stats/trials/stop` `{package}` → delete trial
The backend never calls ComfyUI Manager.
## Frontend (JS)
- **WF-load hook:** wrap `app.loadGraphData` (and/or graph-changed event);
collect node types present in the graph but not in
`LiteGraph.registered_node_types` (unresolved).
- **Classify** unresolved types using:
- `/customnode/getmappings?mode=local` → class_type → pack key
- `/customnode/getlist?mode=local&skip_update=true` → pack install state
- pack state `disabled` → Disabled bucket; `not-installed`/unmappable →
Missing bucket. (Reconcile the getmappings pack key against getlist entries
by id/cnr_id/aux_id — verify field names during implementation.)
- **Workflow tab**, auto-opened when ≥1 unresolved node:
- Disabled rows (by node): `[Enable 7d]` (temp → Manager enable, then
`trials/start`), `[Enable]` (permanent → Manager enable, then `trials/stop`
if previously on trial). Show "on trial — N day-boots left" when applicable.
- Missing rows (by node): `[Install]` → defer to Manager install.
- Actions resolve to the owning package and dedupe.
- **Expiry execution:** on app load / dialog open, `GET /nodes-stats/trials`;
for `expired` packs, disable via Manager (reuse existing disable code) →
`trials/stop` → toast "auto-disabled N unused trial package(s)".
- Enable/disable need a restart to apply in the running session → reuse the
existing restart banner.
## Error handling
- ComfyUI Manager absent → feature inert (no classification, no actions), same
as the existing disable feature.
- HTTP/DB failures → toast + log; never corrupt trial state. Failed expiry
disable keeps the row for a later session (retry).
- Package disabled out-of-band → expiry disable no-ops; row cleaned on stop.
## Testing
- `tests/test_trials.py` (pure logic, mocked dates like existing tests):
- start_trial initializes counters; enable day not counted
- tick_boot_days increments only on a new calendar day (same-day reboots = 1)
- reset_trials_for zeroes the counter
- expiry triggers at `unused_boot_days >= budget`
- start/stop/get round-trips
- Frontend verified manually (graph load → tab → enable/disable → expiry).
## Known implementation risk
The **enable** payload (`/manager/queue/install` + `skip_post_install=true`)
needs the correct `id`/`version` for disabled packs — the same class of detail
as the disable-payload bug already fixed. Verify empirically against a live
disabled package during implementation rather than assuming.
## Files touched
- `tracker.py` — trial table + methods
- `__init__.py` — boot tick, usage reset hook, 3 routes
- `js/nodes_stats.js` — WF-load hook, classification, Workflow tab, actions,
expiry execution
- `tests/test_trials.py` — backend unit tests
- `README.md`, `pyproject.toml` — docs + version bump