action-writer
Create or update an Action in app/Actions following JournalOS conventions (validation, logging, queues, tests).
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Action Writer
This Skill helps you add or update an Action in app/Actions using the project conventions.
When to use this Skill
Use this Skill when:
- Creating a new Action
- Updating an existing Action
Instructions
Quick start
- Pick the action name (verb-first) and its input/output types.
- Create the Action class and implement
execute(). - Add or update unit tests in
tests/Unit/Actions. - Run the smallest relevant test(s), then
composer journalos:unit.
Step 1: Choose the action name and scope
- Actions represent a single user intent (example:
LogHealth,CreateBook,ResetWorkData). - Keep inputs minimal and explicit. Use descriptive parameter names.
- Decide whether the Action returns a model, a scalar, or nothing (
void).
Step 2: Create the Action class
- Use Artisan to create the class:
php artisan make:class Actions/ActionName --no-interaction
- Use
declare(strict_types=1);and theApp\Actionsnamespace. - Default to
final readonly classwith constructor property promotion. - If the Action must store a result (ex:
$this->book), keep itfinalbut notreadonly, and declare a private property for the result. - Always include explicit return types.
Step 3: Implement the action flow
- Typical flow:
- validate input
- perform data changes
- log user action
- update last activity date
- refresh content presence (when journal entry content changes)
- Validate ownership with
ModelNotFoundExceptionwhen a user does not own a journal or entry. - Validate input with
ValidationException::withMessages([...])when values are invalid. - Sanitize user-provided text with
TextSanitizer::plainText(). - Use Eloquent models and relationships. Avoid
DB::. - Queue background work on the
lowqueue:
LogUserAction::dispatch(...)->onQueue('low');
UpdateUserLastActivityDate::dispatch($user)->onQueue('low');
CheckPresenceOfContentInJournalEntry::dispatch($entry)->onQueue('low');
- If the Action mutates a journal entry module, reload the relationship before returning:
$this->entry->load('moduleHealth');
Step 4: Add tests
- Create
tests/Unit/Actions/ActionNameTest.php. - Use
RefreshDatabaseandQueue::fake(). - Use factories with explicit foreign keys. Never use
for()on factories. - Cover:
- happy path
- invalid input (throws
ValidationException) - unauthorized access (throws
ModelNotFoundException) - queued jobs on
lowqueue
Step 5: Run tests and lint
- Run the smallest relevant test file:
php artisan test tests/Unit/Actions/ActionNameTest.php
- Run
composer journalos:unit. - Run Pint:
vendor/bin/pint --dirty
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!