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.
bash
php artisan make:migration create_module_MODULE_NAME_table --no-interaction
- •A module always belongs to a journal entry. Use this pattern:
code
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 toJournalEntry. One module per day, so alwaysHasOne.
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
JournalEntryrelationship method. - •Never use
for()on factories. Setuser_idexplicitly.
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
CreateDefaultLayoutForJournalaction. - •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 likeLogHealth). - •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(seeResetHealthData).
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/partialsfolder. - •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.phpfile. - •Add the view at the right place within the
resources/views/app/journal/entry/show.blade.phpfile.
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
TextSanitizerbefore 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
JournalEntryResourceto 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 testif 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.