| name | module-writer |
| description | Create or update a journal entry module (database, model, actions, views, controllers, API, docs, tests). Use when adding a new journal module or changing an existing module such as mood, health, or obligations. |
Module Writer
This Skill helps you create a module used by journal entries. A module is a small, focused set of attributes for one journal entry (example: Mood tracks daily mood).
When to use this Skill
Use this Skill when:
- Creating a new module
- Updating a module
Instructions
Quick start
- Pick a module name and ModuleType category.
- Add migration + model + factory.
- Add actions + tests.
- Add view + web controller + presenter + tests.
- Add API controller + resource + tests.
- Update docs, Bruno, translations.
Step 1: Write the database migration
- Verify the migration doesn't exist. Create it via Artisan with
--no-interaction.
php artisan make:migration create_module_MODULE_NAME_table --no-interaction
- A module always belongs to a journal entry. Use this pattern:
Schema::create('module_health', function (Blueprint $table): void {
$table->id();
$table->unsignedBigInteger('journal_entry_id');
$table->string('category')->default(ModuleType::BODY_HEALTH->value);
$table->text('health')->nullable();
$table->timestamps();
$table->foreign('journal_entry_id')->references('id')->on('journal_entries')->onDelete('cascade');
});
- A module always has a category from
ModuleType. Do not add new types. Choose the closest existing category.
Step 2: Create related model
- Models live in
App\Models. Follow existing module models (e.g., ModuleHealth). Add a brief header PHPDoc for the model.
- Add a
module{ModuleName} method to JournalEntry. One module per day, so always HasOne.
Step 3: Create the factory
Create a factory for the model.
Step 4: Create tests
- Add a model test for the relationship to
JournalEntry.
- Add a test for the new
JournalEntry relationship method.
- Never use
for() on factories. Set user_id explicitly.
Step 5: Add module to ModuleCatalog class
- All modules are documented within ModuleCatalog so we can reference it later dynamically.
Step 6: Decide if the module should be added to the default layout for journal action
- When we create a journal, some modules are added to the layout by default. It's defined in
CreateDefaultLayoutForJournal action.
- Enable a module by default only if it is broadly applicable to most users on most days, immediately understandable without explanation, low-effort to use, non-sensitive in nature, and delivers clear value even with incomplete data; otherwise, keep it disabled by default and require explicit user opt-in.
Step 7: Create the actions to manage the data
- Create
Log{ModuleName} (follow existing actions like LogHealth).
- Action flow: validate -> log data -> log user action -> update last activity -> refresh content presence.
- Add tests for happy path and edge cases.
- Create
Reset{ModuleName}Data (see ResetHealthData).
Step 8: Update the CheckPresenceOfContentInJournalEntry job
- Add the presence of the data of the new module in
app/Jobs/CheckPresenceOfContentInJournalEntry.php.
- Update the test to reflect it.
Step 9: Create a view that will let users interact with the module
- Views are stored in the
resources/views/app/journal/entry/partials folder.
- If the module has Yes/No buttons, use the existing components.
- If the module uses multiple choices, group the choices together like in the health.blade.php view file.
- If the module uses multiple choices with multiple accepted values, display them like in the primary_obligation.blade.php view file.
- Add the view at the right place within the
resources/views/app/journal/entry/edit.blade.php file.
- Add the view at the right place within the
resources/views/app/journal/entry/show.blade.php file.
Step 10: Create the web controller to pilot the view
- Add a controller to call the right view.
- Controller names should follow project convention and include the module name (example:
HealthController.php).
- Sanitize input with
TextSanitizer before actions.
- Validate inline (no Form Requests). Strings must be strings and within max length.
- Create a presenter for module data.
- Update the main journal entry presenter to load this data.
- Update the JournalEntryShowPresenter presenter.
- Test both presenters.
- Add the appropriate web route.
- Create controller tests for happy path and edge cases.
Step 11: Create the api controller
- Create an API controller that uses the same actions for log/reset.
- Update
JournalEntryResource to include the new data.
- Add API controller tests.
Step 12: Add marketing documentation
- Marketing docs live in
resources/views/marketing/docs/api.
- Add a new module in the modules folder.
- Update the resources/views/marketing/docs/api/partials/journal-entry-response.blade.php partial.
- Update the sidebar to include this new entry.
- Add a new controller for the documentation of this module.
- Add a controller test that asserts the content loads.
Step 13: Add appropriate Bruno API documentation
- Add Bruno tests for the new API methods in
docs/bruno.
Step 14: Update translations
- Run
composer journalos:locale.
- Ensure there are no empty entries in
lang/fr.json.
Step 15: Test and lint
- Run relevant PHPUnit tests for the new module.
- When tests pass, run
composer test if needed for coverage.
Step 16: Add module name to README
- Add the new module to the README file in the appropriate section: the name and the emoji.
Step 17: Add module to the /modules marketing view
- Add the module to the list of modules in the marketing site.
Do NOT forget any step. Be extremely thorough in your analysis. DO NOT FORGET ANY STEP.