Skip to content

Ship async-ops foundation + 9 major features + local Docker dev harness - #13

Open
alexandre433 wants to merge 199 commits into
crivion:mainfrom
alexandre433:main
Open

Ship async-ops foundation + 9 major features + local Docker dev harness#13
alexandre433 wants to merge 199 commits into
crivion:mainfrom
alexandre433:main

Conversation

@alexandre433

Copy link
Copy Markdown

Summary

Built via spec → plan → TDD, with Pest + Vitest + Playwright coverage throughout (85 Pest files / ~495 tests, 26 Vitest files).

What's new

  • Async operations foundation — queued OperationJob convention, audit log (operations table), live Reverb progress streaming, scheduler wired up.
  • Multi-engine databases — MySQL/MariaDB/PostgreSQL via a driver abstraction (DatabaseEngineDriver + EngineManager), replacing hardcoded MySQL-only SQL.
  • DB service control — admin start/stop/restart of the DB engine's systemd service.
  • Backups — scheduled + on-demand DB dump/file backup to local or S3, with retention and restore.
  • Cron tasks — per-user crontab management (validated, sandboxed) with a UI.
  • Notifications — in-app center (bell + unread count) plus email/webhook delivery.
  • User analytics — per-user historical CPU/mem/disk/bandwidth + quota usage (was admin-only before).
  • Alternative PHP runtimes — per-site FrankenPHP switch alongside PHP-FPM.
  • Dashboard/UX polish — process doughnut charts, GPU widget, collapsible sidebar, dark-mode fixes, file-manager and firewall/account safety fixes.
  • Local Docker dev harness (local-dev/) — systemd Ubuntu 24.04 container running the full stack, so sudo/systemctl/certbot/ufw features are testable without a live VPS.
  • Installer clean-room fix — installer now works on a vanilla ubuntu:24.04 host.

Not included

feature/installer-preexisting-hardening (pre-existing-service install safety) is still WIP on a separate branch — will be its own PR later.

P.S.: Yes this was made via Claude. I have a great background mainly in backend PHP, but for frontend i do need help with a lot of things.
Don't worry (much) this was manually tested also, can't guarantee that 100% is working, but most of it is working as intended.
Automated tests were also made to reduce the work in manual tests, but some tests should be always done manually since the AI still can't 100% "get there".

alexandre433 and others added 30 commits June 25, 2026 00:05
Single systemd "VPS-in-a-box" Docker container (Ubuntu 24.04, PID 1)
bind-mounted to /home/laranode_ln/panel. Faithful provisioning + live
stats, SQLite-decoupled Pest, Pebble ACME for SSL, gitignored local-dev/
tooling. Approach live-verified on the WSL2 backend (cgroup2fs, systemd
PID 1 running, systemctl service start confirmed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
7 TDD-style tasks: repo enablers (sqlite Pest + env bin path), systemd
Dockerfile, .env.docker + Pebble SSL patch, idempotent entrypoint,
compose, Makefile + full-boot integration, provisioning/SSL smoke.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
- phpunit.xml: enable sqlite :memory: so the suite runs with no external DB
- config/laranode.php: LARANODE_BIN_PATH env override (prod default unchanged)
- CreateFileTest: skip two host-dependent tests unless LARANODE_SYSTEM_TESTS=1
- gitignore local-dev/ tooling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
User chose to commit local-dev/ tooling (was originally gitignored); resolves
a plan contradiction (every task commits into local-dev/). App/config changes
remain limited to 3 files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Address task-2 review (image-size); PHP RUN gets its own apt-get update
since the base layer now removes /var/lib/apt/lists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Address task-4 review: short-circuit on sentinel to avoid re-seed on re-run;
seed admin from sourced shell vars not env(); mysqladmin -u root + hard-fail
if mysql never starts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Windows make mangles backslash-continued recipes; collapse to single lines.
Entrypoint now force-copies .env.docker so a fresh clone provisions without
hand-editing the bind-mounted .env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
CreateAccountService: default notify to false when not posted (was 500).
RenameFileTest: InMemory adapter can't move directories; use a temp LocalFilesystemAdapter
and remove debug cruft.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
admin can create accounts test called real sudo script → 500.
Process::fake() makes it pass in any env without weakening assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
…ment

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
- compose: pin laranode to 10.30.50.10 so it can't DHCP-grab pebble's
  static 10.30.50.2 (ssl profile) — fixes 'address already in use'.
- Makefile: export MSYS_NO_PATHCONV/MSYS2_ARG_CONV_EXCL so make recipes
  don't rewrite in-container /home,/opt paths to C:/msys64/... when
  calling docker.exe (broke 'make provision' on Windows). No-op on Linux.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Cover both MSYS failure modes (env-stripping breaks compose-plugin discovery;
path rewriting breaks docker exec of in-container scripts) per task-7 review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
- entrypoint: back up an existing .env to .env.local-backup before the
  force-copy (avoid silently clobbering a dev's host .env).
- .env.docker: LOCAL-DEV-ONLY warning banner (insecure fixed creds).
- ssl-manager (local copy): default LARANODE_ACME_SERVER to the Pebble
  sidecar so the panel SSL toggle (sudo, no env passed) targets Pebble
  not real Let's Encrypt; add re-sync header. Verified end-to-end via
  GenerateWebsiteSslAction → ssl_status=active.
- AccountsTest: Process::assertRan so the test fails if the system-user
  script isn't invoked (was asserting only the DB row).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Brings the 'VPS-in-a-box' dev container so feature work can be exercised
locally. Verified: 52 passed / 2 skipped in-container.
Foundation-first (async/progress/audit), then DB-engine abstraction +
Postgres/SQLite, fail2ban, git push-to-deploy + atomic releases, then
backups/cron/monitoring/Adminer; Redis/Memcached + MongoDB last. Each
sub-project gets its own spec->plan->build cycle. Backed by a research
workflow + competitor analysis (Forge/Ploi/RunCloud/cPanel/Laravel Cloud).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Async queued-Job convention + live Reverb progress (streamed output lines,
user-scoped channels) + operations audit table + admin log page + scheduler
hook. Proven by converting SSL generate to async. Roadmap Phase 0 crivion#1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Per user: a real notification system (in-app center + email/webhook) and
user-facing machine/resource analytics (today's stats are admin-only).
Both build on the crivion#1 operations/events/scheduler foundation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
7 TDD tasks: operations table+model, OperationUpdated event+channel+lifecycle,
abstract OperationJob, SSL-generate async conversion, admin audit page,
scheduler hook+prune, React live-progress hook/component + SSL toggle wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
…ight E2E smokes)

Project has no JS test harness; introduce it here so the UI is automatically
tested, not just manually. Live SSL streaming stays component+backend+manual
tested (E2E of it would be flaky).
… tests for Task 7

Project had no JS test harness; Task A (Vitest+RTL+jsdom) and Task B
(Playwright E2E smoke) added as project infra, run before the feature tasks.
Task 7 now ships deterministic component/hook tests. 9 tasks total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4JwS6k6MyYJVv27KRyW2d
alexandre433 and others added 26 commits June 27, 2026 16:53
…ecutive-dot manage.sh

- Test 14: laranode-runtime-unit.sh rejects consecutive-dot domain (..evil.com)
- Test 15: laranode-runtime-unit.sh rejects path-traversal domain (evil.com/../../etc)
- Test 16: laranode-runtime-manage.sh rejects consecutive-dot unit name (foo..bar.service)

Fixes punch-list item 4 system-test coverage gaps flagged in Task 9 review:
direct rejection tests for laranode-runtime-unit.sh domain validation and
consecutive-dot unit name guard in laranode-runtime-manage.sh were absent.
All 16 system tests pass in laranode-lab-phpruntimes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nup under set -e

- Website::$appends used camelCase runtimeLabel but the React page reads runtime_label,
  so runtime labels showed the raw value ("frankenphp"); change to snake_case key +
  add a model test asserting toArray() serializes runtime_label (not runtimeLabel).
- laranode-runtime-install.sh: the post-pipe `if [ $? -ne 0 ]` SHA check was dead under
  set -euo pipefail (the failing pipe aborts first, leaking /tmp/frankenphp); use
  `if ! ... | sha256sum -c -; then rm -f; exit 1; fi`.
php-runtimes (#24, Phase 7): per-site FrankenPHP runtime (v1) via Laravel-Octane-style
app server behind an Apache reverse-proxy, per-site systemd unit + loopback port.
Safe-defaults: php-server mode, non-SSL sites only (+ ACME proxy exception, documented
renewal gap), rollback-to-old-runtime on boot failure, strict unit-name/domain/port
validation across 4 privileged scripts. Built in an isolated worktree+container.
Verified combined: Pest 491 passed / 0 failed, Vitest, build, real-FrankenPHP system
tests. Final review safe-defaults all confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fresh parallel lab containers lacked the crontab binary (the cron-tasks feature
relied on it being present ad-hoc in the original lab), so CronJobSystemTest failed
with "crontab: command not found" on a fresh instance. Install + enable cron during
provisioning so every fresh container can run the cron system tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reenshots

These are local scratch artifacts from UI auditing, not source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The grid used `overflow-scroll`, which forces horizontal and vertical
scrollbars even when the content fits. Switched to `overflow-y-auto` so a
scrollbar appears only when the form actually overflows vertically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The delete button was rendered for every row, including the current user.
Gate it like the impersonate link (`account.id !== auth.user.id`) and add a
backend guard in AccountsController::destroy that aborts 403 on self-deletion,
since the UI guard alone is bypassable via a direct API call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The "Menu" heading and the footer kept `md:block` while collapsed, so on
desktop they consumed the narrow w-14 column and pushed the hamburger toggle
past the `overflow-x-hidden` edge — leaving no visible button to re-expand the
sidebar. Drop `md:block` when collapsed so both hide and the toggle stays put.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The dark class was only applied by React in an effect after mount, so a hard
reload in dark mode flashed light first (FOUC). Inline a tiny head script that
reads localStorage and sets the `dark` class before the body renders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
…lick

The breadcrumb was nested inside the `goBack` block, so the current path was
hidden at the home root and only appeared after entering a subfolder. Render
the breadcrumb unconditionally (it already handles root), keep Back only when
not at root, and switch Back from onDoubleClick to a single onClick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
Enabling UFW with no allow rules (default deny incoming) locks the operator
out of SSH and the panel entirely. Add a hard guard plus a guided safe path:

- Refuse to enable unless an allow rule covers SSH (22) AND the panel/web
  port (80, 443, or the APP_URL port). The check reads `ufw show added`, which
  lists staged rules even while UFW is inactive (unlike `ufw status`). The
  guard lives in the controller so it also protects direct API calls.
- "Safe Setup" stages SSH + HTTP + HTTPS (and the panel port) then enables, so
  turning the firewall on is lockout-proof. SSH is opened from anywhere by
  default, with an option to restrict it to the detected IP (validated).
- Warn in the UI when the firewall is disabled and unprotected.

Covered by pure-logic tests (FirewallSafety), controller tests (Process::fake),
Vitest for the page, and a real-ufw system test gated behind
LARANODE_SYSTEM_TESTS (staging only — never enables, to avoid cutting
connectivity mid-test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
…ery feature

Codify the post-feature gate (Pest, Vitest, npm run build, Playwright on the
live panel) and the rule to pint only changed files, never the whole tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The chart used maintainAspectRatio:false in a height-only container. It mounts
on the first websocket payload — when the parent height can momentarily be 0 —
and chart.js sizes to that and never re-measures a height change, leaving a tiny
doughnut. Derive height from width instead (maintainAspectRatio:true +
aspectRatio), which is always non-zero and which chart.js does observe, and move
the legend to the bottom in a width-capped, centred container.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The firewall Actions call `sudo ufw ...` directly, but no sudoers entry covered
the ufw binary, so every firewall operation failed with "a terminal is required
to read the password" / "a password is required". Add a dedicated laranode-ufw
drop-in (NOPASSWD + !requiretty for /usr/sbin/ufw) and install it from the
installer alongside the other per-domain drop-ins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
GetDatabasesWithStatsService sizes every Database row; for a postgres row whose
underlying database no longer exists, pg_database_size(name) raised SQLSTATE
3D000 and took down the entire Databases page. Scope the size query to existing
databases (SELECT ... FROM pg_database WHERE datname = ?) so a missing db yields
no row and a size of 0 instead of throwing. Covered by a real-PG regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The page title, the "Webhook URL" heading and the table column headers had no
text-colour class, so they inherited the default dark text and were unreadable
on the dark background. Add explicit gray-900/dark:gray-100 (headings) and
gray-700/dark:gray-300 (column headers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
- Copy the new laranode-ufw sudoers drop-in during provisioning so firewall
  operations work in the lab.
- ADMIN_EMAIL/ADMIN_PASSWORD live in .env.docker (Laravel env), not the shell,
  so the admin was seeded with an empty email and you couldn't log in after
  `make up`. Default them in-shell and use updateOrCreate (not firstOrCreate)
  so a stale username='laranode' row gets its email/password repaired too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
Add two doughnut charts above the stat cards: CPU broken down by process with an
Idle slice, and RAM broken down by process with Free + Other-used slices. Process
%CPU is per-core so it is scaled into the overall busy share for a coherent gauge;
process %MEM is already a share of total RAM. The top-process feed is now
subscribed once in AdminDashboard and passed to both the charts and the processes
table (TopProcesses is now presentational) so the two consumers can't fight over
leaving the Echo channel. Doughnuts size from width (aspectRatio) to avoid the
zero-height-mount collapse. Pure share math is unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
With config cached (Reverb needs it), phpunit.xml's DB_CONNECTION=sqlite override
is ignored, so RefreshDatabase ran against the live MySQL panel DB and wiped it
(including the admin user) on every test run. Clear cached config before testing
so the sqlite override applies, then re-cache afterwards, preserving the exit code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
HTTP feature tests need an app key to encrypt sessions/cookies. With config
cleared (required so the sqlite DB override applies and tests don't run against
the live MySQL DB), the key was read from the environment and could be missing,
raising MissingAppKeyException. Pin a fixed test key so tests are self-contained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
Detect a GPU once (at install / on demand) and persist the result to the Option
store; if none is found it is recorded as not-detected and never re-probed on the
per-poll stats path, so GPU-less servers pay no cost. Supports NVIDIA (nvidia-smi)
and AMD (rocm-smi). When a GPU is present the dashboard shows a GPU section —
utilisation and VRAM gauges plus util/VRAM/temp/power cards — fed by the live
systemstats broadcast. A cogwheel in the dashboard header triggers a manual
rescan (e.g. after adding a GPU). Adds the long-missing `options` table migration
(the Option model's first persistent user). Detection/parsing covered by tests
with faked nvidia-smi/rocm-smi output; the rescan endpoint is admin-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
The installer referenced repo files (sudoers drop-ins via visudo) and read .env
before it cloned the repo, so a fresh `curl | bash` aborted at the first
`visudo -c -f <missing file>`. Reordered into clear phases: system packages →
clone + composer + key:generate → repo/.env-dependent plumbing (sudoers, pg
reader) → migrate/seed/build → services. Also:

- install the PostgreSQL **server** (was client-only, so the pg stats-reader
  provisioning had no server to talk to);
- apt-install curl/ca-certificates/sudo/openssl up front (not guaranteed on a
  bare image, but used throughout);
- make the clone idempotent (skip if a checkout is already present) so the
  clean-room test can inject a local tree; LARANODE_REPO overrides the source;
- point the clone + README one-liner at this fork;
- fix the `echo -e"` typo.

Verified end-to-end by the new clean-room install test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
`make up` uses a pre-provisioned image and can't catch installer ordering or
packaging drift. This boots a bare jrei/systemd-ubuntu:24.04, injects the working
tree, runs the REAL laranode-installer.sh end to end, and asserts every service
is active, GET /login is 200, and an admin can authenticate — the independent
checks that matter since the installer runs without `set -e`. Run via
`make install-test` (or `bash local-dev/install-test/run.sh`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
.env.local-backup holds the lab's secrets; the *-dark/pie-fixed jpegs are
verification scratch. Keep both out of git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCGVMrDtcLc1TBjHaeBokt
Features section still described the pre-fork MySQL-only baseline;
Roadmap listed Backup Manager and PHP Manager as future work even
though both already ship. Reflect actual shipped scope (multi-engine
DBs, backups, cron, notifications, analytics, PHP runtimes, async
live progress) and trim the roadmap to what's genuinely unbuilt.
docs/superpowers/{specs,plans} are AI-workflow design/planning
artifacts from building these features, not user-facing docs. Not
useful to an upstream maintainer who doesn't use that tooling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant