plum-cms 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +196 -0
  3. data/README.md +79 -0
  4. data/app/assets/builds/tailwind.css +1 -1
  5. data/app/controllers/plum/cp/entries_controller.rb +88 -5
  6. data/app/controllers/plum/cp/static_cache_controller.rb +12 -0
  7. data/app/controllers/plum/form_submissions_controller.rb +13 -0
  8. data/app/controllers/plum/pages_controller.rb +8 -0
  9. data/app/controllers/plum/theme_assets_controller.rb +2 -0
  10. data/app/javascript/controllers/plum/write_controller.js +174 -0
  11. data/app/models/plum/asset.rb +1 -0
  12. data/app/models/plum/content_type.rb +1 -0
  13. data/app/models/plum/entry.rb +57 -0
  14. data/app/models/plum/entry_term.rb +1 -0
  15. data/app/models/plum/form_definition.rb +1 -0
  16. data/app/models/plum/global.rb +1 -0
  17. data/app/models/plum/nav_item.rb +1 -0
  18. data/app/models/plum/nav_menu.rb +1 -0
  19. data/app/models/plum/site.rb +2 -0
  20. data/app/models/plum/site_setting.rb +1 -0
  21. data/app/models/plum/static_cache_invalidation.rb +30 -0
  22. data/app/models/plum/taxonomy.rb +1 -0
  23. data/app/models/plum/term.rb +1 -0
  24. data/app/services/plum/config_sync.rb +252 -0
  25. data/app/services/plum/draft_diff.rb +177 -0
  26. data/app/services/plum/form_renderer.rb +12 -1
  27. data/app/services/plum/liquid_context.rb +0 -2
  28. data/app/services/plum/site_archive.rb +367 -0
  29. data/app/views/layouts/plum/write.html.erb +140 -0
  30. data/app/views/plum/cp/dashboard/show.html.erb +10 -3
  31. data/app/views/plum/cp/entries/_form.html.erb +2 -2
  32. data/app/views/plum/cp/entries/diff.html.erb +40 -0
  33. data/app/views/plum/cp/entries/edit.html.erb +24 -0
  34. data/app/views/plum/cp/entries/index.html.erb +3 -0
  35. data/app/views/plum/cp/entries/write.html.erb +60 -0
  36. data/config/plum_routes.rb +5 -0
  37. data/db/engine_migrate/20260811090000_add_draft_data_to_plum_entries.rb +5 -0
  38. data/docs/config-as-code.md +103 -0
  39. data/docs/plum-cli.md +356 -0
  40. data/docs/portability.md +40 -0
  41. data/docs/roadmap.md +71 -69
  42. data/docs/static-caching.md +163 -0
  43. data/lib/generators/plum/install/templates/plum_initializer.rb +8 -0
  44. data/lib/plum/configuration.rb +12 -1
  45. data/lib/plum/engine.rb +10 -1
  46. data/lib/plum/static_cache/middleware.rb +61 -0
  47. data/lib/plum/static_cache.rb +103 -0
  48. data/lib/plum/version.rb +1 -1
  49. data/lib/tasks/plum_config.rake +48 -0
  50. data/lib/tasks/plum_portability.rake +62 -0
  51. data/lib/tasks/plum_styles.rake +14 -9
  52. metadata +26 -5
data/docs/plum-cli.md ADDED
@@ -0,0 +1,356 @@
1
+ # The Plum CLI
2
+
3
+ `cli/` (Go, `github.com/tableneeds/Plum/cli`) is the Plum command line. It
4
+ wraps the rake tasks that ship with the engine — `plum:site:export`,
5
+ `plum:site:replace`, `plum:config:sync`, `plum:backup:create`, and so on —
6
+ over a transport of your choosing. **The CLI adds transport and ergonomics,
7
+ never behavior.** Anything it does, you could also do by hand with `ssh` and
8
+ `bin/rails`; it just saves you from typing the same incantation every time
9
+ and from having to remember which task does what.
10
+
11
+ Status: early. Every command has been exercised end-to-end against a local
12
+ Plum install (`via: ssh` with `host: local`), and `via: once` has been
13
+ verified against a live Once deployment on a real VPS (`plum connect`,
14
+ tooling detection, and `plum logs` included). `via: kamal`'s
15
+ argument-building is unit-tested against fake binaries and smoke-tested
16
+ against the real `kamal` executable's argument parser, but hasn't been run
17
+ against a real Kamal fleet yet.
18
+
19
+ ## Look and feel
20
+
21
+ On a terminal, the CLI speaks heroku-style: `▸` step headers, animated
22
+ spinners that settle into timed `✓ Downloading archive (700ms)` lines,
23
+ remote rake output framed behind a dim `│` gutter (so the server's chatter
24
+ reads as quoted material, not the CLI's own voice), and arrow-key prompts
25
+ in `plum connect`. The accent color is, naturally, plum — adaptive to
26
+ light and dark backgrounds.
27
+
28
+ All of it degrades automatically: pipe any command, run it in CI, set
29
+ `NO_COLOR`, or use `TERM=dumb`, and you get plain sequential text with the
30
+ classic `Prompt [default]:` stdin reads — scripted answers and log parsing
31
+ keep working unchanged.
32
+
33
+ ## Install
34
+
35
+ No published binary yet — build from source:
36
+
37
+ ```bash
38
+ cd cli
39
+ go build -o plum .
40
+ # put ./plum somewhere on your PATH, e.g.:
41
+ mv plum ~/.local/bin/plum
42
+ ```
43
+
44
+ ## The built-in tutorial
45
+
46
+ ```
47
+ plum tutorial
48
+ ```
49
+
50
+ An interactive, full-screen tour of Plum and the CLI — eight short
51
+ chapters covering the content model, themes, writing mode and drafts,
52
+ static caching, config as code, and every CLI command, rendered as styled
53
+ markdown right in your terminal (bubbletea + glamour). Chapters slide in
54
+ on spring physics (harmonica), a gradient progress bar tracks where you
55
+ are, the CLI chapters have **live demos** — press `d` and watch
56
+ `plum pull` or a connect health check type itself out, spinners and all —
57
+ and the tour ends with a three-question quiz. Harmonica does real work:
58
+ a splash intro drops a bouncing plum across the springing wordmark,
59
+ wrong quiz answers shake on an underdamped spring, and finishing the
60
+ quiz bursts projectile-physics confetti.
61
+
62
+ It's also a game. Progress persists (chapters read, demos watched, quiz
63
+ passed) so the tour resumes where you left off; everything earns XP with
64
+ plum-themed ranks in the header (Seedling through Orchardist). CLI
65
+ chapters have typing challenges — press `t` and type the real command
66
+ with per-character feedback; success rolls the demo. And the title
67
+ screen hides **Plum Drop** (press `p`): catch falling plums with an
68
+ arrow-key basket, chase combos, and defend a persisted high score — it's
69
+ endless, so play until you've had enough. Navigate with ←/→, scroll
70
+ with ↑/↓, jump with 1–9, quit with q. Piped, it prints the chapters as
71
+ plain markdown.
72
+
73
+ And because it's bubbletea, it also serves over SSH (charm's wish):
74
+
75
+ ```
76
+ plum tutorial --serve # listens on :2222
77
+ plum tutorial --serve :2345 # or wherever
78
+ ```
79
+
80
+ Anyone who can reach the port gets the full animated tour with nothing
81
+ installed — `ssh -p 2222 your-server` and they're in. Sessions run only
82
+ the tutorial program (no shell, no exec); any or no public key is
83
+ accepted since the content is public. The host key persists at
84
+ `~/.config/plum/tutorial_host_ed25519` so returning visitors don't get
85
+ host-key warnings.
86
+
87
+ ## Two layers of configuration
88
+
89
+ This mirrors the Firebase CLI's model deliberately, because it solves the
90
+ same two problems: "how does this directory reach its server" and "how do I
91
+ work with a project without cd-ing into its directory first."
92
+
93
+ 1. **`plum.yml`** — lives in a site's own repo, defines its **remotes**
94
+ (servers). This is the source of truth for how to reach that one site.
95
+ 2. **The global project registry** (`~/.config/plum/config.yml`, or
96
+ `$XDG_CONFIG_HOME/plum/config.yml`) — lives on your dev machine, not in
97
+ any repo. It's just a name → directory index plus one "active project"
98
+ pointer, so you can run `plum pull` from your home directory and have it
99
+ know you mean the Table Needs site, without `cd`-ing there first.
100
+
101
+ ### `plum.yml`
102
+
103
+ The easy way — guided setup, no YAML editing:
104
+
105
+ ```bash
106
+ plum connect 203.0.113.5
107
+ ```
108
+
109
+ Walks you through: checking for a local SSH key (offers to `ssh-keygen` one
110
+ if you don't have one), testing whether key-based login to the server
111
+ already works, offering to run `ssh-copy-id` if it doesn't, optionally
112
+ adding a `Host` alias to your real `~/.ssh/config` (so `ssh
113
+ plum-production` also works, and plum.yml can reference the alias instead
114
+ of raw host/user), and finally writing `plum.yml` for you.
115
+
116
+ It also **detects how the repo deploys** and asks the right questions for
117
+ that shape: `config/deploy.yml` means Kamal, a `Dockerfile` without one
118
+ usually means Once (you confirm the guess either way). A plain ssh remote
119
+ gets asked for the app's path on the server; a Once remote gets asked for
120
+ the app's hostname (`once_app`) instead, since the app lives in a container
121
+ rather than at a path; a Kamal remote needs neither.
122
+
123
+ Connect asks as little as it can get away with — ideally nothing.
124
+ **On an already-configured project, bare `plum connect` skips the interview
125
+ entirely**: it shows what plum.yml says and verifies it still works (SSH
126
+ login, server tooling, the app actually deployed), exiting nonzero if a
127
+ check fails — a doctor, usable from CI. The wizard only runs on first
128
+ setup, with `--reconfigure`, or when you pass a new ip/host explicitly.
129
+
130
+ When the wizard does run, **re-runs prefill every prompt from the existing
131
+ plum.yml** — Enter through everything and you reproduce the current setup.
132
+ Even a brand-new project isn't a blank slate: **the host prompt offers the
133
+ plum-* aliases already in your ~/.ssh/config** (servers this CLI set up
134
+ before) as a pick-list, so connecting a second site to the same VPS is all
135
+ Enter keys. And for Once remotes, once SSH login works it **asks the
136
+ server itself** (`once list`) and offers the deployed apps as a pick-list —
137
+ with your previous choice highlighted — instead of a blank hostname
138
+ prompt.
139
+
140
+ For Kamal and Once shapes it then **checks the server's tooling** — Docker
141
+ Engine for both, plus the `once` binary for Once — and offers to install
142
+ whatever's missing (`curl -fsSL https://get.docker.com | sh`, `curl
143
+ https://get.once.com | sh`), each only with your explicit confirmation. It
144
+ stops short of deploying the app itself: pushing images and registry
145
+ credentials stay in your hands. Finally it verifies what it can — that
146
+ `bin/rails` exists at the path you gave (ssh), or that `once list` shows
147
+ your app (once) — so you find out immediately if something's off instead of
148
+ on your first `plum pull`. Along the way it registers the project in the
149
+ global registry under the directory's name (and makes it the active project
150
+ if none is set), so `plum use <name>` and `--project <name>` work without a
151
+ separate `plum projects add`.
152
+
153
+ `plum connect` (with no IP) prompts for the host interactively too. Run it
154
+ again to add a second remote (e.g. staging) to an existing plum.yml — it
155
+ merges in the new remote rather than overwriting the file, though note that
156
+ re-saving via `connect` does not preserve hand-written comments in an
157
+ existing plum.yml.
158
+
159
+ The manual way — writes an editable template with comments intact:
160
+
161
+ ```bash
162
+ plum init
163
+ ```
164
+
165
+ ```yaml
166
+ default: production # which remote to use when none is named
167
+
168
+ remotes:
169
+ production:
170
+ host: your-server.example.com
171
+ user: deploy
172
+ path: /var/www/your-site
173
+ # rails: bin/rails # how to invoke rails in `path` (default shown)
174
+ # ssh_args: ["-p", "2222"] # extra ssh/scp args: custom port, identity file, etc.
175
+
176
+ staging:
177
+ host: staging.example.com
178
+ user: deploy
179
+ path: /var/www/your-site-staging
180
+ ```
181
+
182
+ Omit the remote name on any command to use `default:` (or the only remote,
183
+ if there's just one).
184
+
185
+ ### The project registry (fleet management)
186
+
187
+ ```bash
188
+ plum projects add table-needs ~/Work/TableNeeds # register a directory
189
+ plum projects list # see what's registered
190
+ plum use table-needs # make it the active project
191
+ plum projects remove table-needs # forget it (files untouched)
192
+ ```
193
+
194
+ Once a project is active, every command works from anywhere:
195
+
196
+ ```bash
197
+ cd ~ # nowhere near the TableNeeds repo
198
+ plum use table-needs
199
+ plum pull # pulls TableNeeds' production data into TableNeeds' local site
200
+ ```
201
+
202
+ Resolution order for any command (`pull`, `push`, `check`, `backup`, `logs`, `run`):
203
+
204
+ 1. `--project NAME` on the command itself — always wins.
205
+ 2. A `plum.yml` in the **current directory** — so working inside a repo
206
+ behaves exactly like today, unaffected by whatever project is active
207
+ elsewhere.
208
+ 3. The globally **active project** (`plum use`) — the fallback that makes
209
+ fleet commands work from anywhere.
210
+ 4. Otherwise: an error telling you to `plum init` or `plum use`.
211
+
212
+ ```bash
213
+ plum pull --project table-needs # one-off target, doesn't change what's active
214
+ ```
215
+
216
+ ## Commands
217
+
218
+ ```
219
+ plum connect [ip-or-host]
220
+ ```
221
+ Guided setup — see above. The only command that touches your local SSH
222
+ config or generates keys, and only with your confirmation at each step.
223
+
224
+ ```
225
+ plum pull [remote] [--yes]
226
+ ```
227
+ Replaces your local site with the remote's: exports an archive there,
228
+ downloads it, and runs `plum:site:replace` locally. Content-only — the
229
+ underlying database engine doesn't have to match (Postgres in production,
230
+ SQLite locally works fine; see `docs/config-as-code.md` and the site archive
231
+ format). Prompts for confirmation unless `--yes` is passed.
232
+
233
+ ```
234
+ plum push [remote] [--prune] [--force]
235
+ plum check [remote]
236
+ ```
237
+ Upload the local `plum/` config-as-code directory (see
238
+ `docs/config-as-code.md`) and apply or verify it against the remote. On a
239
+ project with no `plum/` yet, both commands offer to run
240
+ `bin/rails plum:config:export` for you on the spot (interactively only —
241
+ scripts and CI still get a hard error, since a fresh export would make
242
+ the drift check meaningless). `check`'s exit code is nonzero on
243
+ drift — wire it into CI. `--prune` deletes content types missing from the
244
+ files; `--force` allows that even when they still have entries.
245
+
246
+ Push moves *structure* (content types, fieldsets), pull moves *content* —
247
+ the same asymmetry as "push code, pull data", because in Plum's model your
248
+ content model **is** code. Neither touches entry data on the other side.
249
+ (`plum sync` still works as an alias for push; it was renamed because sync
250
+ sounded two-way.)
251
+
252
+ ```
253
+ plum backup [remote]
254
+ ```
255
+ Runs `plum:backup:create` on the remote (writes a timestamped archive on the
256
+ server; doesn't download it — that's what `pull` is for).
257
+
258
+ ```
259
+ plum logs [remote] [--follow]
260
+ ```
261
+ Shows the app's recent logs; `--follow` (or `--tail`) streams them until you
262
+ Ctrl-C, heroku-style. On a terminal, structured JSON log lines (the
263
+ slog/thruster shape modern Rails containers emit) are prettified — dim
264
+ local-time stamps, color-coded levels and HTTP statuses, request lines
265
+ compacted to `GET /up 200 (2ms)` with the noisy fields dropped — while
266
+ plain Rails log text passes through untouched. Piped or under `NO_COLOR`
267
+ you get the raw bytes the server sent, so `plum logs | jq` still works.
268
+
269
+ What "the logs" means depends on the transport: plain ssh tails
270
+ `log/production.log` under the app path, Kamal runs `kamal app logs` (all
271
+ hosts, not just the primary), and Once streams `docker logs` from the app's
272
+ container on the server.
273
+
274
+ ```
275
+ plum run [remote] -- TASK [ENV=value ...]
276
+ ```
277
+ Escape hatch: run any rake task on the remote. `plum run production --
278
+ plum:site:export ARCHIVE=/tmp/x.zip` is what `pull` does internally, spelled
279
+ out.
280
+
281
+ ## Transports (`via:`)
282
+
283
+ Every remote picks a `via:`. It only changes *how* the command above reaches
284
+ the server — the rake tasks and their behavior are identical either way.
285
+
286
+ ### `via: ssh` (default)
287
+
288
+ Plain `ssh`/`scp`, inheriting your `~/.ssh/config`, SSH agent, and jump
289
+ hosts. Needs `host`, usually `user` and `path`.
290
+
291
+ ### `via: kamal` — for apps deployed with [Kamal](https://kamalmanual.com)
292
+
293
+ ```yaml
294
+ remotes:
295
+ production:
296
+ via: kamal
297
+ # kamal_bin: bin/kamal # default: bin/kamal if present, else `kamal` on PATH
298
+ # kamal_config: config/deploy.yml
299
+ # kamal_destination: staging # for config/deploy.staging.yml
300
+ ```
301
+
302
+ No `host` or `path` needed — Kamal already knows your servers from
303
+ `config/deploy.yml` and owns its own SSH connection. Commands run as
304
+ `kamal app exec --reuse --primary "<command>"`, matching the pattern this
305
+ repo's own `config/deploy.yml` already uses for its `console`/`shell`
306
+ aliases. `--primary` targets one host even if you deploy to several, so a
307
+ rake task like `plum:site:export` runs exactly once.
308
+
309
+ ### `via: once` — for apps deployed with [Once](https://once.com)
310
+
311
+ ```yaml
312
+ remotes:
313
+ production:
314
+ via: once
315
+ host: plum-production # how to SSH into the server (IP or ~/.ssh/config alias)
316
+ once_app: your-app.example.com # the hostname you gave `once deploy --host ...`
317
+ # user: root # if host isn't an alias that already sets it
318
+ # once_bin: once # path to once ON THE SERVER (default shown)
319
+ ```
320
+
321
+ Unlike Kamal, Once's CLI runs **on the server**, not on your dev machine —
322
+ it talks straight to the local Docker daemon. So this transport is really
323
+ ssh underneath: it connects to `host` (same semantics as `via: ssh` —
324
+ aliases, agents, and `ssh_args` all work) and runs
325
+ `once exec <once_app> <command> [args...]` there.
326
+
327
+ That's why there are two identifiers: `host` is how you *reach the box*,
328
+ `once_app` is which *app on it* you mean — commonly different values, e.g.
329
+ an SSH alias pointing at a bare IP vs. the app's public DNS hostname. One
330
+ server can run several Once apps; `once_app` picks yours.
331
+
332
+ `plum logs` is the one command that doesn't go through `once exec`: the
333
+ Rails image logs to the container's stdout (there's no `log/production.log`
334
+ inside it), and deployed Once has no `logs` subcommand — so the CLI finds
335
+ your app's container by the `once` label Once stamps on it and streams
336
+ `docker logs`, the same daemon Once itself drives.
337
+
338
+ ## What this doesn't do (yet)
339
+
340
+ - **No deploys.** `plum connect` will install the *tooling* a fresh server
341
+ needs (Docker Engine, the `once` binary — each with your confirmation),
342
+ but actually deploying the app — building images, registry credentials,
343
+ `once deploy` / `kamal deploy` — stays in your hands. Kamal and Once
344
+ already do that well; wrapping them for exec is as far as this goes on
345
+ purpose (see `docs/config-as-code.md` and the CLI design notes for why
346
+ re-inventing deploy tooling isn't the goal).
347
+ - **No `--all` / fleet-wide command yet.** Today you target one project (or
348
+ one remote) per invocation. Running one command across every registered
349
+ project is a natural next step, not yet built.
350
+ - **No downloaded database snapshot for SQLite-in-production setups.**
351
+ `pull` always goes through the portable site archive, which works
352
+ regardless of the production database engine. A faster SQLite-specific
353
+ path (`sqlite3 prod.db "VACUUM INTO ..."`) was considered and deliberately
354
+ skipped — the archive is the one code path that works whether production
355
+ runs SQLite or Postgres, and duplicating that logic for a marginal speedup
356
+ wasn't worth the two-path maintenance cost.
@@ -0,0 +1,40 @@
1
+ # Site export, import, backup, and restore
2
+
3
+ Plum 0.3 archives are versioned ZIP files containing a JSON manifest and the
4
+ original bytes of every attached asset. The manifest carries schemas, entries,
5
+ relationships, translations, taxonomies, navigation, globals, forms, fieldsets,
6
+ settings, revisions, and submissions. Import remaps database identifiers so an
7
+ archive can be restored into a different database without collisions.
8
+
9
+ ## Export and import
10
+
11
+ ```sh
12
+ bin/rails plum:site:export ARCHIVE=/safe/location/site.plum.zip SITE_ID=1
13
+ bin/rails plum:site:import ARCHIVE=/safe/location/site.plum.zip NAME="Imported site" DOMAIN=example.com
14
+ ```
15
+
16
+ `SITE_ID` is optional when exporting and defaults to the first site. `NAME` and
17
+ `DOMAIN` are optional import overrides. Import always creates a new site; it
18
+ never overwrites an existing site.
19
+
20
+ ## Backup and restore
21
+
22
+ ```sh
23
+ bin/rails plum:backup:create SITE_ID=1 DIRECTORY=/var/backups/plum
24
+ bin/rails plum:backup:restore ARCHIVE=/var/backups/plum/plum-site-1-TIMESTAMP.plum.zip
25
+ ```
26
+
27
+ The backup task writes a timestamped archive. Restore uses the same safe,
28
+ new-site import path. Assets are checked against their recorded byte size and
29
+ checksum before the database transaction commits. Missing, corrupt, or
30
+ unsupported archives fail without leaving a partially imported site.
31
+
32
+ Backups must be copied off the application host and tested regularly. An archive
33
+ does not include application code, environment secrets, users, or the host
34
+ database outside the selected Plum site.
35
+
36
+ ## Archive compatibility
37
+
38
+ The manifest currently uses format version `1`. Plum rejects archive versions
39
+ it does not understand instead of guessing. Future format changes will either
40
+ remain readable or ship an explicit migration path.
data/docs/roadmap.md CHANGED
@@ -1,76 +1,79 @@
1
1
  # Roadmap
2
2
 
3
- Plum's roadmap is organized by outcomes rather than promised dates. Items move
4
- as the product is exercised in real applications and the Rails community gives
5
- feedback.
6
-
7
- ## Now: Make Plum Real and Adoptable
8
-
9
- The current milestone is to build and publish the Plum marketing and
10
- documentation site as a standalone Rails application powered by Plum and
11
- SQLite.
12
-
13
- ### Product foundation
14
-
15
- - Publish a clear vision, product principles, and supported use cases.
16
- - Build `plumcms.org` with Plum as an external dependency.
17
- - Define a portable production layout for the SQLite database, uploaded assets,
18
- and mutable site data.
19
- - Package the site as an ONCE-compatible Docker application.
20
- - Exercise installation, initialization, health checks, upgrades, backup, and
21
- restore on a real VM.
22
- - Keep SQLite and PostgreSQL verification in CI.
23
-
24
- ### Documentation
25
-
26
- - Document installation and the first editable page.
27
- - Document sites, content types, fields, entries, and publishing.
28
- - Document blocks, assets, relationships, taxonomies, navigation, globals, and
29
- forms.
30
- - Document Liquid themes and host content sources.
31
- - Document embedded authentication, authorization, tenancy, and routing.
32
- - Document standalone SQLite and embedded PostgreSQL deployments.
33
- - Publish configuration, troubleshooting, and upgrade references.
34
-
35
- ### Distribution
36
-
37
- - Publish the `plum` gem.
38
- - State supported Ruby and Rails versions.
39
- - Establish semantic versioning and an upgrade policy.
40
- - Test fresh external installations and upgrades in CI.
41
- - Provide a production-ready container contract and example application.
42
-
43
- ## Next: Editorial Confidence and Portability
44
-
45
- Once the public site proves the basic workflow, focus on the features editors
46
- and agencies need to trust Plum in production.
47
-
48
- - Draft preview.
49
- - Revisions and rollback.
50
- - Scheduled publishing.
51
- - Multi-entry relationships.
52
- - Reusable blocks and sections.
53
- - Content, schema, and asset export/import.
54
- - Document hierarchy, tables of contents, and site search.
55
- - SEO metadata, canonical URLs, sitemaps, feeds, and redirect management.
56
- - Stronger asset organization and image editing.
57
- - Form spam protection and improved submission workflows.
58
- - Tested backup and restore commands.
3
+ Plum's roadmap is organized by outcomes rather than promised dates. Priorities
4
+ move as the product is exercised in real Rails applications.
59
5
 
60
- ## Later: The Rails Content Ecosystem
6
+ ## Shipped: Plum 0.2.0 Authoring Foundation
61
7
 
62
- After installation and production operation are dependable, make Plum easier to
63
- adopt repeatedly and extend publicly.
8
+ Plum 0.2.0 established the practical authoring surface:
64
9
 
65
- - A thin `plum` CLI for new sites, diagnostics, themes, export/import, upgrades,
66
- and packaging.
67
- - Starter applications for common content-site use cases.
68
- - A documented extension contract for fields, blocks, and content sources.
69
- - A collection of high-quality open themes and blocks.
10
+ - a visual blueprint builder with 21 field types, reusable fieldsets, nested
11
+ structures, validation, field widths, sections, and conditions;
12
+ - assets with single and multiple image fields, metadata, focal points, and
13
+ responsive variants;
14
+ - relationships, taxonomies, navigation, globals, forms, and Liquid themes;
15
+ - drafts, scheduled publishing, immutable revisions, and rollback;
16
+ - localized entries, locale-aware public routes, and a live-only content API;
17
+ - registry-backed custom field types and packaged Tailwind control-panel styles.
18
+
19
+ See [the Statamic parity matrix](statamic-parity.md) for the supported surface.
20
+
21
+ ## Now: Plum 0.3.0 — Production Readiness
22
+
23
+ The 0.3.0 milestone makes Plum safe to adopt, move, recover, and operate outside
24
+ its development repository.
25
+
26
+ ### 1. Portability and recovery
27
+
28
+ - Versioned site export/import covering schemas, entries, relationships,
29
+ taxonomies, navigation, globals, forms, settings, and assets.
30
+ - Tested backup and restore commands with integrity checks and clear failure
31
+ behavior.
32
+ - A documented archive format that remains readable across compatible releases.
33
+
34
+ ### 2. Real-world installation
35
+
36
+ - Build `plumcms.org` as a standalone Rails application using the released gem.
37
+ - Test fresh external installations and 0.2.x upgrades in CI.
38
+ - Publish a production Docker contract and SQLite deployment example.
39
+ - Exercise health checks, upgrades, persistence, backup, and restore on a VM.
40
+
41
+ ### 3. Publishing and discovery
42
+
43
+ - SEO metadata, canonical URLs, redirects, XML sitemaps, and feeds.
44
+ - Document hierarchy, breadcrumbs, generated tables of contents, and site search.
45
+ - Secure, shareable preview links for draft and scheduled content.
46
+
47
+ ### 4. Editorial operations
48
+
49
+ - Asset folders, search, replacement, cropping, and richer transformations.
50
+ - Granular editorial roles, approvals, and publish permissions.
51
+ - Form spam protection and improved submission review/export workflows.
52
+
53
+ ### 5. Documentation
54
+
55
+ - Complete installation and first-page guides.
56
+ - Document every supported field and content primitive.
57
+ - Document standalone SQLite and embedded PostgreSQL operation.
58
+ - Publish configuration, troubleshooting, extension, upgrade, backup, and
59
+ restore references.
60
+
61
+ ## Next: Plum 0.4.0 — Repeatable Adoption
62
+
63
+ - A thin `plum` CLI for diagnostics, export/import, backup/restore, upgrades,
64
+ themes, and packaging.
65
+ - Starter applications for common publishing use cases.
70
66
  - Theme scaffolding, validation, packaging, and distribution tools.
67
+ - A collection of high-quality open themes and blocks.
71
68
  - Importers for common CMS and structured-data formats.
72
- - Optional content APIs for applications that genuinely need them.
69
+ - Addon discovery and a documented compatibility contract.
70
+
71
+ ## Later: The Rails Content Ecosystem
72
+
73
+ - Translation-service integrations and richer localization workflows.
74
+ - Agency-oriented multisite operations and reusable project recipes.
73
75
  - Community examples, case studies, talks, and contribution programs.
76
+ - Broader content APIs where real applications demonstrate the need.
74
77
 
75
78
  ## Plum 1.0
76
79
 
@@ -92,7 +95,6 @@ It requires:
92
95
 
93
96
  ## Not on the Near-Term Roadmap
94
97
 
95
- Plum is not currently trying to become a general-purpose admin framework, a
96
- freeform visual design tool, an ecommerce platform, or a hosted headless CMS.
97
- Those products solve different problems. Plum's focus is managed content that
98
- belongs inside Rails.
98
+ Plum is not trying to become a general-purpose admin framework, a freeform
99
+ visual design tool, an ecommerce platform, or a hosted headless CMS. Plum's
100
+ focus is managed content that belongs inside Rails.