Configuring Horizon
coollabsio/coolify
A skill your agent uses whenever the user mentions Horizon by name in a Laravel context.
OpenLitterMap v5 architecture reference. An agent skill from OpenLitterMap/openlittermap-web.
$ npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install OpenLitterMap/openlittermap-web olm-architecture --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.ai/skills/olm-architecture .claude/skills/olm-architecture && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "olm-architecture" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architecture into .claude/skills/olm-architecture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "olm-architecture", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architectureType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install OpenLitterMap/openlittermap-web olm-architecture --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.ai/skills/olm-architecture .agents/skills/olm-architecture && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "olm-architecture" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architecture into .agents/skills/olm-architecture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "olm-architecture", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install OpenLitterMap/openlittermap-web olm-architecture --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.ai/skills/olm-architecture .cursor/skills/olm-architecture && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "olm-architecture" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architecture into .cursor/skills/olm-architecture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "olm-architecture", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/OpenLitterMap/openlittermap-web.git --path .ai/skills/olm-architecture--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install OpenLitterMap/openlittermap-web olm-architecture --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.ai/skills/olm-architecture .gemini/skills/olm-architecture && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "olm-architecture" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architecture into .gemini/skills/olm-architecture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "olm-architecture", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install OpenLitterMap/openlittermap-web olm-architectureInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .github/skills && cp -r skills-src/.ai/skills/olm-architecture .github/skills/olm-architecture && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "olm-architecture" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architecture into .github/skills/olm-architecture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "olm-architecture", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install OpenLitterMap/openlittermap-web olm-architecture --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/OpenLitterMap/openlittermap-web.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.ai/skills/olm-architecture .opencode/skills/olm-architecture && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "olm-architecture" agent skill from https://github.com/OpenLitterMap/openlittermap-web/tree/master/.ai/skills/olm-architecture into .opencode/skills/olm-architecture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "olm-architecture", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
olm-architectureOpenLitterMap v5 architecture reference. An agent skill from OpenLitterMap/openlittermap-web.
Olm Architecture is an agent skill from OpenLitterMap/openlittermap-web. OpenLitterMap v5 architecture reference. Use this skill whenever working on OLM backend code, Laravel controllers, services, events, tests, Redis, metrics, tags, photos, teams, admin, leaderboards, or any part of the OpenLitterMap codebase. Also trigger when the user mentions MetricsService, VerificationStatus, PhotoTags, AddTagsToPhotoAction, TagsVerifiedByAdmin, rewardXpToAdmin, school pipeline, teacher approval, clustering, or any OLM-specific concept. This skill should be used BEFORE writing any OLM code to…
Its SKILL.md is about 5.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Backend & APIs, covering Backend development. It works with Redis and Laravel. The repository describes itself as: https://opengeospatialdata.springeropen.com/articles/10.1186/s40965-018-0050-y. The licence is GPL-3.0.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit ac688aa. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are php, json and sql).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Olm Architecture loads about 5.2k tokens when it runs. Until then it costs about 154 tokens; SKILL.md has 1,736 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from OpenLitterMap/openlittermap-web at commit ac688aa, republished under its GPL-3.0 licence (© OpenLitterMap). 1,736 words, ~5,217 tokens.
.claude/skills/olm-architecture/SKILL.md (or your agent's skills folder).This is the canonical reference for OLM's v5 architecture. Read this before writing any backend code.
Nothing writes metrics directly. Every tag creation, edit, or deletion flows through a pipeline that ensures metrics, leaderboards, Redis, and the database stay in sync.
User/Admin/Teacher tags a photo
↓
AddTagsToPhotoAction::run($userId, $photoId, $tags) ← wrapped in DB::transaction()
├── Resolves tags:
│ ├── Object tags: uses category_litter_object_id (CLO ID), auto-resolves category
│ ├── Custom-only: $tag['custom'] = true, $tag['key'] = "dirty-bench"
│ ├── Brand-only: $tag['brand_only'] = true
│ └── Material-only: $tag['material_only'] = true
├── Creates PhotoTag + PhotoTagExtraTags rows
├── Calls GeneratePhotoSummaryService
│ ├── Builds summary JSON from PhotoTags (numeric ID keys)
│ ├── Calculates XP via XpScore enum multipliers
│ └── Writes photo.summary, photo.xp, photo.total_tags
↓
updateVerification() — WHO tagged it?
├── Trusted user → verified = ADMIN_APPROVED, fires TagsVerifiedByAdmin
├── Non-trusted user → verified = UNVERIFIED (0), fires TagsVerifiedByAdmin
│ (Photo NOT on map, but user gets immediate leaderboard credit)
├── School student → verified = VERIFIED (1), does NOT fire event
│ (Waits for teacher approval — safeguarding invariant)
↓
TagsVerifiedByAdmin event (fires for ALL non-school users)
→ ProcessPhotoMetrics listener
→ MetricsService::processPhoto($photo)
├── Computes fingerprint from summary JSON
├── If new (processed_at null): creates metric rows
├── If re-edit (processed_at set): computes delta
├── Upserts metrics table (5 timescales × 4 location scopes × 2 user modes)
├── Updates Redis (stats hashes, XP ZSETs, bitmaps, HLLs)
└── Sets photo.processed_at, processed_fp, processed_tags, processed_xpKey distinction: TagsVerifiedByAdmin fires for ALL non-school users at tag time. Trusted users also get verified = ADMIN_APPROVED (photo visible on map). Non-trusted users stay at verified = 0 (photo NOT on map, but user IS on leaderboard). Only school students' photos wait for teacher approval.
enum VerificationStatus: int
{
case UNVERIFIED = 0; // Uploaded, no tags (also: tagged by non-trusted non-school user)
case VERIFIED = 1; // Tagged by school student (awaiting teacher)
case ADMIN_APPROVED = 2; // Approved by admin/trusted/teacher
case BBOX_APPLIED = 3; // Bounding boxes drawn
case BBOX_VERIFIED = 4; // Bounding boxes verified
case AI_READY = 5; // Ready for model training
}Critical rule: Use ->value >= VerificationStatus::ADMIN_APPROVED->value (not == 2) when checking if a photo is public-ready. Photos at BBOX_APPLIED+ are also approved.
The enum is cast on the Photo model: 'verified' => VerificationStatus::class. Use ->value for >=/< comparisons, direct enum for === equality.
Used everywhere: admin approve, teacher approve, batch approve.
$updated = Photo::where('id', $photo->id)
->where('is_public', true)
->where('verified', '<', VerificationStatus::ADMIN_APPROVED->value)
->update(['verified' => VerificationStatus::ADMIN_APPROVED->value]);
if ($updated > 0) {
event(new TagsVerifiedByAdmin(
photo_id: $photo->id,
user_id: $photo->user_id,
country_id: $photo->country_id,
state_id: $photo->state_id,
city_id: $photo->city_id,
team_id: $photo->team_id
));
rewardXpToAdmin();
}Why atomic: Prevents double-processing. If two admins approve simultaneously, the WHERE clause ensures only one succeeds. The second gets $updated = 0 and no event fires.
Always pass all 6 named constructor args to TagsVerifiedByAdmin. Missing args cause listener failures.
Single writer rule. Only MetricsService writes to the metrics table and Redis metric keys. No other code may increment/decrement counters. Never use DB::table('metrics')->increment() or Redis::hincrby() directly.
processed_at is null: first processing → doCreate() (creates metrics + increments users.xp)processed_at is set: re-processing → doUpdate() (computes delta, applies corrections to users.xp)metrics table rows: 5 timescales × 4 location scopes × 2 (aggregate user_id=0 + per-user user_id>0)RedisMetricsCollector (inside DB::afterCommit)photo.processed_at, processed_fp, processed_tags, processed_xp$team->isSchool() check — school photos skip entirely (deferred to approval). The gate is a school team check, NOT an is_public check.is_public = false by user preference) still get immediate upload XPprocessed_atprocessPhoto() routes to doUpdate() (delta-based) since processed_at is set$photo->delete() — hard sequencing ruleprocessed_at is not null// CORRECT order
if ($photo->processed_at) {
$this->metricsService->deletePhoto($photo);
}
$photo->delete(); // soft delete
// WRONG — metrics can't read a deleted photo
$photo->delete();
$this->metricsService->deletePhoto($photo); // too lateEvery tagged photo has a summary JSON column — a flat array of tag entries, one per photo_tags row:
{
"tags": [
{
"clo_id": 42,
"category_id": 1,
"object_id": 5,
"type_id": 3,
"quantity": 2,
"picked_up": true,
"materials": [1, 4],
"brands": { "7": 1 },
"custom_tags": [45]
}
],
"totals": {
"litter": 3,
"materials": 3,
"brands": 1,
"custom_tags": 0
},
"keys": {
"categories": { "1": "alcohol" },
"objects": { "5": "bottle" },
"types": { "3": "beer" },
"materials": { "1": "glass" },
"brands": { "7": "heineken" }
}
}Structure: tags[] — each entry maps 1:1 to a photo_tags row. materials and custom_tags are ID arrays (set membership, qty = parent tag quantity); brands is an {id: quantity} map (independent quantities); totals.litter = sum of object quantities; keys is a reverse-lookup for display (empty sub-maps omitted). Generated by GeneratePhotoSummaryService from PhotoTag rows. If summary is null, the photo has no tags and cannot be approved.
photo_tags (
id, photo_id,
category_id, -- FK → categories (auto-resolved from object)
litter_object_id, -- FK → litter_objects
category_litter_object_id, -- FK → category_litter_object pivot (CLO ID)
litter_object_type_id, -- FK → litter_object_types (e.g., "beer" in "beer bottle")
custom_tag_primary_id, -- for custom-only tags (no category/object)
quantity, picked_up,
created_at, updated_at
)
photo_tag_extra_tags (
id, photo_tag_id,
tag_type, -- 'material' | 'brand' | 'custom_tag'
tag_type_id, -- FK → materials / brandslist / custom_tags_new
quantity, index,
created_at, updated_at
)v4 vs v5: v4 stored tags in 16+ separate category tables (smoking_id, food_id, etc on photos). v5 uses the unified PhotoTags table. Legacy v4 endpoints and the ConvertV4TagsAction shim have been removed (2026-03-01). Mobile now uses v3 endpoints with CLO format. Category FK columns on photos are deprecated.
Frontend sends CLO IDs: The web frontend sends category_litter_object_id (pre-resolved from the search index). Backend auto-resolves category_id from the CLO pivot. Category need NOT be sent separately.
No unique constraint on photo_tags: There is no unique constraint on (photo_id, category_litter_object_id, litter_object_type_id). Duplicate CLO+type combinations are possible (each row is a separate PhotoTag). Extra-tag deduplication (materials/brands) is handled via upsert within a single PhotoTag row, not across rows. Do NOT add a unique constraint expecting one row per (photo, CLO, type) combination.
getNewTags() conditional inclusion: UsersUploadsController::getNewTags() conditionally includes category and object only when both resolve (i.e., when both category_id and litter_object_id are non-null). Extra-tag-only PhotoTags (brand/material/custom-only) return category: null and object: null.
Upload error contract: UploadPhotoRequest::failedValidation() returns { success: false, error: <code>, message: <string>, errors: {...} }. resolveErrorCode() maps validation failures to typed string codes: no_exif, no_gps, no_datetime, duplicate, invalid_coordinates, validation_error. Mobile clients should read the error field for programmatic handling.
Hard rule: School team photos have is_public = false until a teacher approves them.
// PhotoObserver::creating() — automatic privacy
if ($photo->team_id) {
$team = Team::find($photo->team_id);
if ($team && $team->isSchool()) {
$photo->is_public = false;
}
}
// ALL public-facing queries MUST use this:
Photo::public() // →where('is_public', true)
// or:
->where('is_public', true)School pipeline: student uploads → is_public = false → NO upload metrics (deferred) → teacher reviews in Facilitator Queue → teacher approves → is_public = true + verified = ADMIN_APPROVED → TagsVerifiedByAdmin fires → processPhoto() → doCreate() handles full XP (upload + tag) in one pass + increments users.xp.
Admin queue never sees school photos (WHERE is_public = true excludes them). Teachers are the sole approvers for their team via the Facilitator Queue (3-panel admin-like UI).
Users can control the public visibility of their photos independently of the school pipeline.
users.public_photos (boolean, default true): A per-user default. New photos inherit this value unless overridden.
Visibility precedence (highest to lowest):
is_public = false (enforced by PhotoObserver, cannot be overridden)is_public param in upload requestpublic_photos defaulttruePrivate-by-choice photos (non-school, is_public = false by user preference):
is_public = false photo)School photos (is_public = false enforced):
recordUploadMetrics() is skipped (school team check, not is_public check)Per-photo toggle: PATCH /api/v3/photos/{id}/visibility — owner-only, requires auth:sanctum. Blocked for school team photos (403). PhotoObserver marks affected tiles dirty on is_public change so the map updates correctly.
Laravel Permission 6, web guard.
| Role | Access |
|---|---|
superadmin | Everything + trust management + Horizon |
admin | Photo review, approve, edit tags, delete |
helper | Tag editing only |
school_manager | Manage school team, approve student photos (invite email sent on grant) |
Check with: $user->hasRole('admin') or $user->hasRole('superadmin')
Admin middleware checks: hasRole('admin') || hasRole('superadmin')
Nav.vue isAdmin includes 'superadmin' role (not just 'admin' and 'helper').
All keys use hash tags {...} for Redis Cluster compatibility:
{g}:stats → HASH (global stats: uploads, tags, litter, xp, ...)
{g}:lb:xp → ZSET (global XP leaderboard, user_id → xp)
{g}:hll → HyperLogLog (contributor count)
{c:ID}:stats → HASH (country stats)
{c:ID}:lb:xp → ZSET (country leaderboard)
{s:ID}:stats → HASH (state stats)
{s:ID}:lb:xp → ZSET (state leaderboard)
{ci:ID}:stats → HASH (city stats)
{ci:ID}:lb:xp → ZSET (city leaderboard)
{u:ID}:stats → HASH (per-user stats: uploads, xp, litter)
{u:ID}:bitmap → Bitmap (activity tracking)Key builder: RedisKeys::global() → {g}, RedisKeys::country($id) → {c:$id}, etc.
rewardXpToAdmin() updates both MySQL users.xp AND Redis {g}:lb:xp sorted set.
Redis is a derived cache. Rebuildable from the metrics table. Never treat Redis as source of truth.
Photo model uses SoftDeletes trait. $photo->delete() sets deleted_at, does not remove the row. Eloquent auto-applies global scope excluding soft-deleted records. Raw queries need explicit whereNull('deleted_at').
Types: community and school. Stored in team_types table (resolve via $team->getTypeNameAttribute()). Do NOT hardcode type IDs — they vary between environments.
School teams enforce extra safeguarding: MasksStudentIdentity trait masks student names as "Student N" (deterministic, based on team_user.id join order). Student identity never exposed publicly.
School teams must NOT be is_trusted — trust bypasses teacher approval entirely.
Key methods: $team->isSchool(), $team->isLeader($userId), $team->hasSafeguarding()
School team leaders have a 3-panel verification queue (same layout as admin queue):
FacilitatorQueueFilters (status toggle, date range)PhotoViewer (reused from tagging v2)UnifiedTagSearch + ActiveTagsList (reused from tagging v2)Backend: TeamPhotosController returns new_tags (CLO format), accepts CLO-based tag edits, provides member stats with safeguarding.
/teams route uses TeamsHub.vue (replaces old sidebar layout). Three states: no teams (create/join landing), active team (header + stats + tabs), no active team (team picker). Tabs: Overview, Photos, Map, Members, Settings, Leaderboard, Approval Queue (school), Participants (school+sessions). Privacy defaults: leaderboards = false for all new teams, safeguarding = true enforced for school.
SchoolManagerInvite mailable queued when school_manager role is granted (artisan command or admin toggle). Two CTAs: Upload → /upload, Create Team → /teams/create. Not sent on revoke.
Config-driven thresholds in config/levels.php. 12 levels from "Noob" (0 XP) to "SuperIntelligent LitterMaster" (1M+ XP).
LevelService::getUserLevel($xp) returns: level, title, xp_into_level, xp_for_next, xp_remaining, progress_percent.
User model next_level accessor calls LevelService. Frontend reads user.next_level.title.
AddTagsToPhotoAction::calculateXp() uses XpScore enum multipliers:
| Action | XP | Note |
|---|---|---|
| Upload | 5 | Base per photo |
| Object | 1 | Per item (special objects: small=10, medium=25, large=50, bagsLitter=10) |
| Brand | 3 | Per brand (uses brand's own quantity) |
| Material | 2 | Per material (uses parent tag's quantity — set membership) |
| Custom Tag | 1 | Per custom tag (uses parent tag's quantity) |
| Picked Up | 5 | Per object (×quantity) where photo_tags.picked_up=true. Objects only — no bonus for brand/material/custom-only tags |
All list/leaderboard endpoints use: total_tags, total_photos, total_members, created_at, updated_at. Never use old names: total_litter, total_images, tags, photos, contributors, members.
Grid-based, 9 zoom levels (0,2,4,6,8,10,12,14,16). Two tiers:
Photos need verified >= ADMIN_APPROVED and is_public = true to appear in clusters.
PhotoObserver marks affected tiles dirty when photos are verified/moved. Team dirty tracking was removed — team clustering is on-demand only.
TestCase uses RefreshDatabase + Redis flush + TagKeyCache::forgetAll()auth:sanctum routes: use actingAs($user) with NO guard argauth:api routes (legacy mobile): use actingAs($user, 'api')verified, is_public, summary, country_id explicitlyVerificationStatus assertions: compare enum directly or use ->value for orderingEvent::fake([TagsVerifiedByAdmin::class]) — fakes specific events, others still fireapp()[PermissionRegistrar::class]->forgetCachedPermissions()TeamType::create(['team' => 'community', 'price' => 0]) — price has no default1033+ tests passing (1 skipped). See testing-patterns skill for full details.
is_public = true in admin/public queries — exposes school data->verified == 2 instead of ->verified->value >= VerificationStatus::ADMIN_APPROVED->value — misses BBOX+ states and breaks due to enum castwhereNotNull('summary') in queue queries — untagged photos can't be approvedDB::transaction() — partial tag state causes summary corruptionTagsVerifiedByAdmin for school students — breaks safeguarding invariantresult_string or total_litter — deprecated write-only columns. Use summary and total_tagsmetrics tabledoesntHave('photoTags') for untagged filter — use WHERE verified = 0actingAs() guard with route middleware — actingAs($user) for auth:sanctum, actingAs($user, 'api') for auth:apiRead the relevant file before working in that area:
| Document | Covers |
|---|---|
readme/API.md | Comprehensive API endpoint reference (source of truth) |
readme/Tags.md | Tag hierarchy, summary JSON, XP calculation |
readme/Teams.md | Teams architecture, permissions, safeguarding, facilitator queue |
readme/SchoolPipeline.md | School approval pipeline (critical data flow) |
readme/Metrics.md | Metrics pipeline and aggregation |
readme/Leaderboards.md | Leaderboard system (Redis ZSETs + MySQL) |
readme/Admin.md | Admin verification system, queue UI, roles |
readme/Upload.md | Photo upload pipeline |
readme/Clustering.md | Map clustering system |
readme/Locations.md | Location and geography system |
readme/Mobile.md | Mobile app & v4-to-v5 tag conversion shim |
readme/Profile.md | User profile, settings, privacy, public profiles |
readme/Email.md | Email subsystem — campaign send + ledger, SNS suppression feedback loop, subscribe/unsubscribe |
© OpenLitterMap, GPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .ai/skills/olm-architecture of OpenLitterMap/openlittermap-web.
Open the folder on GitHubat commit ac688aa
Olm Architecture next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Olm Architecture this skillOpenLitterMap/openlittermap-web | 134 | — | ~5.2k | Automated safety check: Pass | GPL-3.0 | |
| Configuring Horizoncoollabsio/coolify | 63k | 4 repos | ~898 | Automated safety check: Pass | MIT | |
| Pulse DevelopmentMineTrax/minetrax | 115 | 1 repos | ~1.7k | Automated safety check: Pass | MIT | |
| AsyncTheBeardedBearSAS/claude-craft | 107 | — | ~741 | Automated safety check: Pass | MIT | |
| Laravel Database ExpertHoangNguyen0403/agent-skills-standard | 571 | — | ~802 | Automated safety check: Pass | MIT | |
| Laravel Sessions MiddlewareHoangNguyen0403/agent-skills-standard | 571 | — | ~830 | Automated safety check: Notes | MIT |
coollabsio/coolify
A skill your agent uses whenever the user mentions Horizon by name in a Laravel context.
MineTrax/minetrax
Handles Laravel Pulse setup, configuration, and custom card development.
TheBeardedBearSAS/claude-craft
Architecture async-first avec messaging et queues (Symfony Messenger, Laravel Queue, Ecotone).
HoangNguyen0403/agent-skills-standard
Optimize Laravel queries with subqueries, joinSub, Redis cache-aside patterns, and read/write connection splitting.
HoangNguyen0403/agent-skills-standard
Configure Redis session drivers, register security-header middleware, and prevent session fixation in Laravel.
yaojingang/GEOFlow
Operate/develop GEOFlow CLI/Laravel/admin/API, topics/专题 and topic tasks, theme libraries/replication, sites/leads/Agent, channel sync and legacy yao-geoflow-cli/design/template migration.
OpenLitterMap/openlittermap-web
Styles applications using Tailwind CSS v3 utilities. An agent skill from OpenLitterMap/openlittermap-web.
OpenLitterMap/openlittermap-web
AchievementEngine, AchievementRepository, milestone checkers, AchievementsSeeder, userachievements pivot, AchievementsController API, and achievement evaluation flow.
OpenLitterMap/openlittermap-web
AdminController, photo approval, tag editing, deletion, MetricsService integration, admin middleware, verification queue, and admin XP.
OpenLitterMap/openlittermap-web
REST API endpoints, route structure, auth guards, request/response contracts, error patterns, and the full API surface for web SPA and mobile clients.
OpenLitterMap/openlittermap-web
ClusteringService, tile keys, dirty tiles/teams, clustering commands, ClusterController GeoJSON API, PhotoObserver dirty marking, and map cluster rendering.
OpenLitterMap/openlittermap-web
LeaderboardController, Redis sorted sets for all-time XP rankings, per-user metrics rows for time-filtered rankings, rewardXpToAdmin, and leaderboard privacy.
Categories
OpenLitterMap v5 architecture reference. An agent skill from OpenLitterMap/openlittermap-web. Olm Architecture is an agent skill from OpenLitterMap/openlittermap-web. OpenLitterMap v5 architecture reference.
Olm Architecture fits situations like: working on OLM backend code; laravel controllers; any part of the OpenLitterMap codebase; the user mentions MetricsService.
Run `npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a claude-code`. Or copy the skill folder (.ai/skills/olm-architecture in OpenLitterMap/openlittermap-web) into .claude/skills/olm-architecture in your project. Claude Code loads it when a task matches its description.
Run `npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a codex`. Or copy the skill folder (.ai/skills/olm-architecture in OpenLitterMap/openlittermap-web) into .agents/skills/olm-architecture in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add OpenLitterMap/openlittermap-web --skill olm-architecture -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/olm-architecture, .gemini/skills/olm-architecture, .github/skills/olm-architecture and .opencode/skills/olm-architecture in your project.
SKILL.md names no scripts, command-line tools or credentials: Olm Architecture is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Olm Architecture is published under the GPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.2k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Olm Architecture: Configuring Horizon (coollabsio/coolify, 63k stars), Pulse Development (MineTrax/minetrax, 115 stars), Async (TheBeardedBearSAS/claude-craft, 107 stars) and Laravel Database Expert (HoangNguyen0403/agent-skills-standard, 571 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
OpenLitterMap (a GitHub organization) maintains it in OpenLitterMap/openlittermap-web, which has 134 GitHub stars. The repository holds 16 skills in this directory. The repository was last updated on September 14, 2026.
Source: OpenLitterMap/openlittermap-web on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.