Simple Slides is a responsive and text-first presentation tool that keeps your audience engaged, and is built with:
- Laravel 13 (PHP 8.5)
- Filament 5 — powers the admin panel and all authentication
- Inertia 3 + Vue 3 + TypeScript
- Tailwind CSS 4
- PostgreSQL 15
Presentations built with Simple Slides typically focus on:
- Prioritizing text-content
- Low amount of content per slide
- Many slides, and changing through slides quickly
-
Docker (everything else runs inside Laravel Sail's containers)
-
PHP 8.5 on the host, only for the very first
composer install. If your host PHP is older, run the install through Docker instead:docker run --rm \ -u "$(id -u):$(id -g)" \ -v "$(pwd):/var/www/html" \ -w /var/www/html \ laravelsail/php85-composer:latest \ composer install --ignore-platform-reqs -
Node 24 is what CI uses. It runs inside the container, so you do not need it on the host.
Ideally, you should set an alias for the sail command, to make it much shorter to use:
alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'Note: If you have run a Laravel Sail project before, you may have already created this alias.
- Locate the .env.example file in your project directory.
- Create a new file named .env in the same directory.
- Copy the entire content of the .env.example file and paste it into the newly created .env file.
Note: This file will hold your environment variables.
Run below commands in your terminal
composer install
sail npm install
sail artisan key:generateThis project uses Laravel Sail, which is basically a built-in Docker wrapper for Laravel applications. View their documentation for more details.
This project does not use Sail's default ports. .env.example pins a unique
port for every container so that several Laravel applications can run side by
side without colliding:
| Service | Env var | Host port | URL |
|---|---|---|---|
| Application | APP_PORT |
7070 | http://localhost:7070 |
| Admin panel | — | 7070 | http://localhost:7070/admin |
| Vite dev server | VITE_PORT |
51730 | — |
| Mailpit dashboard | FORWARD_MAILPIT_DASHBOARD_PORT |
18025 | http://localhost:18025 |
| Mailpit SMTP | FORWARD_MAILPIT_PORT |
10250 | — |
| PostgreSQL | FORWARD_DB_PORT |
54320 | — |
| Redis | FORWARD_REDIS_PORT |
63790 | — |
Two things to watch for:
APP_PORTandAPP_URLmust be changed together. If you move the app off 7070 and leaveAPP_URLpointing at the old port, generated URLs, signed invitation links, and asset URLs all break.- Port changes only take effect on container create, not restart. After
editing any of these, run
sail down && sail up -d.
Use the PostgreSQL and Redis ports above to connect host-side tools such as TablePlus or a Redis client.
# In one terminal, to run the app:
sail up -d
# Then run the migrations and seeders (this command will end)
sail artisan migrate:fresh --seed
# Then, build, watch, and hot reload for front-end changes
sail npm run devThe app is then at http://localhost:7070, and any mail the app sends is caught by Mailpit at http://localhost:18025.
To log in, go to http://localhost:7070/admin — Filament owns authentication, so
that is the only login entry point. Use any of the following credentials (found
in database/seeders/DatabaseSeeder.php):
- Email:
admin@example.comfor an admin user, ortest@example.comfor a non-admin user. - Password: Both accounts use
password.
sail npm run buildNote that build runs vue-tsc before Vite, so a type error fails the build,
not just the lint step.
Via Laravel pint, which is a code-formatter following Laravel's best practices. It is not a linter, but CI does check it — a push that is not Pint-clean fails the Back-end Format job.
# Format everything
sail bin pint
# Format only what you changed
sail bin pint --dirty
# Check without writing (what CI runs)
sail bin pint --testLinting on the back-end (e.g. Laravel) is done via larastan, which is a PHPStan wrapper for Laravel.
Linting on the front-end (e.g. Vue w/ Typescript) is done with vue-tsc, which
is a first-party wrapper for Vue around tsc. See more here:
https://vuejs.org/guide/typescript/overview
# Back-end (Laravel)
sail bin phpstan analyse
# Front-end (Vue)
sail npx vue-tscTests on the back-end (e.g. Laravel) are written using Pest, and are a mostly integration and end-to-end tests, with some unit tests sprinkled in. Currently there are no browser tests.
Tests on the front-end (e.g. Vue w/ Typescript) are written using vitest, along with some other test utilities like Vue Test Utils and Mock Service Worker.
# Back-end (Laravel)
sail artisan test
# Front-end (Vue), in watch mode
sail npx vitest
# Front-end, single run (what CI does)
sail npm run test
# Front-end, with a coverage report
sail npm run test:coverageThere are also composer entry points for the back end: sail composer run test clears the config and runs the suite, and sail composer run dev starts
the Vite dev server.
.github/workflows/ci.yml runs on every push and pull request, against PHP 8.5
and Node 24. Runs are cancelled when a newer commit lands on the same ref.
| Job | Command |
|---|---|
| Front-end Lint | vue-tsc |
| Back-end Lint | phpstan analyse |
| Back-end Format | pint --test |
| Front-end Test | vitest --run |
| Back-end Test | pest |
| Build | Builds and pushes the production image to Docker Hub |
Pint is deliberately not part of CI, so formatting will not fail a build —
run sail pint locally.
Production does not use Sail. It runs the image built by docker/app/Dockerfile,
which serves nginx + php-fpm under s6, plus a queue worker and the cron-driven
scheduler.
docker-compose.prod.yml— app, PostgreSQL, and Redis.env.prod.example— copy to.env.prodand fill in
Production differs from local in a few meaningful ways:
- Cache and sessions use Redis rather than the file driver.
- Media and Filament uploads live on Digital Ocean Spaces (
FILESYSTEM_DISK=do,MEDIA_DISK=do), so theDO_*credentials are required. - Mail goes out through Sendgrid instead of Mailpit.
- The image runs a
queue:workworker, but.env.prod.exampleshipsQUEUE_CONNECTION=sync, so nothing reaches it until you point the queue at Redis.
Thumbnails are rendered by Browsershot, which drives a headless Chrome through puppeteer. The Sail image already has every library Chrome needs, but the Chrome binary itself is downloaded separately and is not part of the image:
sail npx puppeteer browsers install chromeRun this once after sail npm install. You also need to re-run it:
- After the container is rebuilt or recreated. The download lives in
/home/sail/.cache/puppeteerinside the container, which is not on the bind mount, so it does not survive. - After
puppeteeris upgraded, because each puppeteer release pins a specific Chrome build.
In either case, the "Generate Thumbnail" action fails with
Error: Could not find Chrome (ver. <version>). Re-running the install command
above fixes it.
To check what is currently installed:
sail ls /home/sail/.cache/puppeteer/chromeThe directory name (e.g. linux-153.0.8010.36) must match the version in
node_modules/puppeteer-core/lib/puppeteer/revisions.js.
Settled decisions, non-obvious traps, and standing constraints live in
.ai/rules/. Start at .ai/rules/index.md, which maps file globs to the rule
file that covers them. CLAUDE.md and AGENTS.md carry the framework and
package guidelines for AI agents working in this repo.
One constraint worth knowing before you touch package.json:
Do not upgrade
typescriptpast 6.x. TS 7 is the native Go port, and its package exports drop the JS compiler API. That breaksvue-tscoutright and also failsvite buildinside@vue/compiler-sfc. See.ai/rules/general.mdfor the details.
- VS Code + Vue - Official (and disable Vetur).
The separate "TypeScript Vue Plugin (Volar)" extension and Volar's Take Over Mode are both obsolete — everything they did is built into Vue - Official as of v2, so installing the Vue extension alone is enough.
TypeScript cannot handle type information for .vue imports by default, so we
replace the tsc CLI with vue-tsc for type checking, both locally and in CI.
In the editor, the Vue - Official extension makes the TypeScript language
service aware of .vue types.