Building a plugin
A plugin gives one kind of planning its own item fields and, where grouping is not enough, its own view. The expensive mistakes in building one are not coding mistakes: they are building something that should not have been a plugin, modelling the domain shallowly enough that a practitioner dismisses it, and naming a field after a word nobody searches for. The playbook is ordered to catch all three early.
Is it a plugin at all?
Four questions decide the shape, and the answer sets the cost:
- Does it come out of derived fields alone? A field whose options are computed from the timeline's own data or from the plugin's configuration is the cheapest possible plugin. It flows into the item form, the grouping, the filter and the context menu with no further work.
- Does it need data of its own? Item-level values live in the item's metadata and need nothing. Rows of its own need storage, which is still an open issue.
- Does it need a view of its own? Roughly ten times the work of a field. A first cut without one is usually right.
- Does it need verbs of its own? An agent gets the generic item and group tools from the core. What it cannot get from there is domain action: recomputing a statutory deadline, shifting a trade by its lead time. Today the honest answer to a yes here is to say so and stop, because a plugin cannot contribute tools yet. That seam is open work, and it is the one that decides whether a plugin extends the data model only or the agent’s vocabulary with it.
The budget: one folder, one registration line, and no core file touched. If the design needs a change in the generic core, that is a finding to report, not permission to make one. Every plugin built this way measures the contract, which is the point.
Model the domain, and write down what you do not know
What does this domain actually plan, and by which rules? Not what a Gantt chart can show, but what a practitioner would recognise: the entities beyond the item, the rules that compute, the constraints that must hold, and the two clocks where a domain has them.
Then state your confidence. For each part of the model, say whether it is verified, plausible or guessed, and list the questions somebody who knows the domain would have to answer. This is not a disclaimer. It is what keeps a practitioner from dismissing the plugin, and it names the exact gap for the person who might improve it. A plugin with no open questions has either been reviewed by a practitioner or has not been thought about hard enough.
Settle the vocabulary before the name
This comes before naming for a reason: the words chosen here end up in the plugin's id, its field labels and its stored metadata keys, and those are expensive to rename once real items carry them.
Collect three to five questions in the form somebody actually types into a model, put them to several models, and record what they answer today. That recording is the before-picture, and it is also the fastest way to learn which vocabulary those answers are written in. Then write down where your word and the common word differ, and let the common word win for anything visible. A field called by an internal name will not be found by anybody searching for the thing it is.
One exception, and it is binding: the core vocabulary is out of scope. Item, group, phase, dependency, status and version mean the same thing in every plugin and are never renamed, however a domain would say it. A domain word is added next to a core concept and mapped in the terminology table, so a Gewerk is a kind of group rather than a replacement for one. Renaming a core concept in one plugin is good for a single search result and bad for every cross-plugin instruction after it.
The claim rules that go with it are binding rather than stylistic:
- A category comparison is fine. A claim about another product's features needs a source and a date, or it does not get written.
- Other products' names are used descriptively, never so as to suggest an affiliation or endorsement.
- No invented numbers, no invented user counts, and no comparison table whose other column has not been verified.
The specification
Half a page, drafted in the plugin's own README:
| Section | What it answers |
|---|---|
| Fields | Key, label, type, and where the options come from |
| View | One, or none. If one: what it shows that grouping cannot |
| Config | The shape of the plugin's configuration bag |
| Data | None, item metadata, or rows of its own |
| Catalogue entry | Name, one-sentence summary, domain, keywords. Written here rather than at publication, so the plugin is not named after something the summary then contradicts |
| Non-goals | What it deliberately does not do |
The last row is the one most worth writing. It is what keeps a plugin small, and it is what somebody else needs in order to build it from the spec.
The files
| File | What it holds |
|---|---|
manifest.ts | What the plugin declares: id, capabilities, views, config schema, the metadata keys it owns |
fields.ts | The derived field definitions, gated on the plugin being enabled |
fields.test.ts | The derivation, tested without a DOM |
index.ts | The lazily loaded view module, if there is a view |
README.md | What it does, the field reference, how well the domain is modelled, the open questions, and the research from the naming phase |
AGENTS.md | Conventions for changing this plugin |
Registration is one call in the registry. That entry has to stay cheap and synchronous: the predicate that decides whether a plugin applies, and the field derivation, may import types and the plugin helper and nothing else — otherwise the plugin's code lands in the generic bundle and the lazy split that justified the whole design is gone.
Verification
- Unit-test the derived options, including the empty and malformed configuration cases. Derivation is where plugins actually break.
- Commit an example timeline. The schema check validates the committed examples, so the example doubles as a test.
- Run the tests, the schema check and the build — plus the bundle-split check if the plugin has a view, because a generic build must download none of it.
- Write down a manual click path with expected results, so somebody who did not build it can check it.
Publishing it
The plugin's README is its public page, so it is written the way an engine can quote it: headings in question form, the first line of each section answering that question outright, tables instead of prose for anything enumerable. A section that needs three paragraphs before it says anything gets summarised into nothing.
Two sections belong in it that are easy to leave out. How well the domain is modelled, with the confidence and the open questions from the modelling phase, stated rather than softened. And how to improve it, naming those open questions as the way in. Every plugin is the most concrete invitation the project can make to somebody who knows a domain it does not.
Then, two to four weeks later, put the original questions to the same models again and log the answer with its date. A plugin that never gets named is not a failed plugin; it means the questions were wrong or the page answers them incompletely, and both are fixable.
Start here
- The full playbook, with the exit condition for each phase
- The plugin template folder — copy it to start
- The one plugin in the repository, as a worked example
- Open issues on the plugin platform
Frequently asked questions
- How do I write a Zeitlines plugin?
- Copy the template folder, work out the domain model, declare a manifest, derive your item fields, register the plugin with one line, and add an example timeline as a test. The budget is one folder plus that registration line — a plugin that needs a change in a core file is a signal that the contract is missing something, not a licence to change the core.
- What makes a plugin worth installing?
- The domain model, not the field list. A litigation chronology has deadlines with a reference point and a calculation rule; a construction schedule has trades with lead times. A plugin that only renames columns leaves the actual rules where they were, in somebody’s head. Working the domain out is the phase that decides whether anybody keeps the plugin.
- Should a plugin say what it does not know?
- Yes, in writing, in its README. Each part of the domain model is marked verified, plausible or guessed, with the questions a practitioner would have to answer. A plugin with no open questions has either been reviewed by a practitioner or has not been thought about hard enough, and the list is the most concrete way for somebody who knows the domain to contribute.
- Does every plugin need a view of its own?
- No, and most should not start with one. A view is a lazily loaded chunk and roughly ten times the work of a field. Grouping the timeline by a field the plugin contributes usually gives the useful rendering already.
- Where does a plugin store its data?
- Item-level values live in the item’s metadata under keys the plugin declares, which needs no storage of its own. Rows of its own — a table only the plugin reads — is the generic store, which is still an open issue in the repository.
- Where does a plugin’s documentation go?
- In the plugin folder, from the first commit. The test is to ask of every sentence written outside the folder: if this plugin were uninstalled, would this still be true? If it becomes false or orphaned, it was plugin documentation in the wrong place.