mcpField
W
wi-app

wi-app

github.com/wonder-imageDevelopment0170 installsUpdated 1d ago
Repository

About wi-app

wi-app is an agent skill for working on wonder-image/app as a Composer framework package rather than a standalone site. It helps developers place changes in the framework, a site, or an external module and follow the package’s architecture and security conventions.

Key features

  1. 01Distinguishes framework, site, and module work
  2. 02Maps tasks to architecture references
  3. 03Documents model and resource extension rules
  4. 04Covers API, CSRF, and security conventions

Use cases

  1. 01Classifying a requested change before editing
  2. 02Finding the relevant framework reference for a task
  3. 03Checking placement and validation rules for a component or API change

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/wonder-image/skills --skill wi-app

After installing, type/wi-appin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
architecture.md
component-catalogue.md
model-and-resource.md
permissions-and-users.md
scheduler.md
security.md
workflows.md

Details

From the project README

Wonder Image App

Overview

Use this skill to work inside wonder-image/app as a framework package, not as a standalone app. Keep the framework-versus-site boundary explicit before changing code, running commands, or deciding where new logic belongs.

Glossary

Shared vocabulary across wi-app and wi-site (identical wording in both skills):

  • framework — wonder-image/app (Composer package, github.com/wonder-image/app). Lives at the repo root when wi-app is active; lives at vendor/wonder-image/app/ inside a site.
  • lib — wonder-image/lib (npm package, github.com/wonder-image/lib). The JS / CSS design system. Source at node_modules/wonder-image/src/; compiled copy at assets/lib/wonder-image/dist/.
  • scaffold — wonder-image/new-site (github.com/wonder-image/new-site). The site template. The scaffold itself is also a valid site.
  • site — any project whose composer.json depends on wonder-image/app (i.e. installs the framework under vendor/wonder-image/app). Typically the scaffold or a project derived from it. Canonical term — prefer "site" over older mixed uses like "consumer project" / "consumer app".
  • module — external Composer package wonder-image/<slug> discovered via Composer (or filesystem fallback). Ships its own models / resources / routes via module.json + a ModuleInterface entrypoint.
  • custom/ — site-only directory for project pages, components, layouts, routes, config, and helpers. Never present inside the framework.
  • app/Models / app/Resources — site PSR-4 roots (App\Models\... / App\Resources\...). Distinct from the framework's class/App/Models / class/App/Resources (Wonder\App\Models\... / Wonder\App\Resources\...).

When to switch to wi-site

Switch to wi-site when any of these signals are true — the work belongs in a site repo, not in the framework:

  • the repo's composer.json does not declare "name": "wonder-image/app"; instead it lists wonder-image/app under require (the framework is installed as a dependency).
  • a docs/ai/PRODUCT.md exists (or, on sites not yet migrated, a PRODUCT.md at the repo root) declaring a Wonder site (site_type: landing / corporate / blog / ecom / rsvp) — that file only exists on sites, never inside the framework itself.
  • vendor/wonder-image/app/ exists at the repo root — the framework is installed under vendor/, so this is a site.
  • the requested change lives under custom/, app/Models, app/Resources, lang/, assets/{ASSETS_VERSION}/, or any path that does not exist inside the framework repo.
  • the task is "add a page", "translate strings", "register a permission key for this site", "edit color tokens", "use a .wi-* component / lib class", or any work that uses (rather than extends) the framework.
  • you are about to patch a file under vendor/wonder-image/app/. Do not. Either reshape the change to live in custom/ / app/ (still wi-site), or open the change against the framework repo itself (then wi-app applies there).

Start Here

  1. Classify the request before editing anything.
  2. Decide whether the change belongs to the framework, a site, or an external module package.
  3. Read only the reference file that matches the task:
    • references/architecture.md for bootstrap, registries, routes, env resolution, or module loading
    • references/model-and-resource.md for the Model / Resource extension API, schemas, repeater fields, and CustomPageSchema
    • references/permissions-and-users.md for the permission builder API, permission keys, and shared user-management resources
    • references/workflows.md for change placement, validation, and common gotchas
    • references/security.md for CSRF, cookies, auth helpers, uploads, redirects, exports, SQL escaping, error/log hygiene, backups and consent

Core Rules

  • Treat wonder-image/app as a Composer library. The real runtime lives in the site that installs it under vendor/wonder-image/app.
  • Assume php forge ... commands are site-side commands unless you confirm otherwise. Do not expect a forge executable in this package root.
  • Prefer class/App/* for new logic. Touch legacy app/* only when the runtime still depends on it.
  • API handlers live under app/http/api/*. Do not reintroduce package handlers under app/api/*; call routed /api/... endpoints via Path::$api, Path::$appApi, and Path::$apiDT.
  • API bearers are verified by Wonder\Api\Token\BearerVerifier (signature, exp, hash_equals against api_users.token, authority, IP/domain); reuse it instead of decoding JWTs by hand. Mint with ApiTokens::issue(), rotate with regenerateApiToken(). Never print a bearer in HTML (Credentials::appToken(), ApiTokens::pageToken()): pages print const API_TOKEN = ''; and the lib omits an empty Authorization. A request without bearer to an endpoint that allows page tokens is @system page access when Endpoint::sessionRequestAllowed() holds (same origin, active session, Csrf::verify() for non-GET/HEAD) and BearerVerifier::verifySession() passes; isPageToken() is true and row visibility uses the session user. To renew after expiry, a site page fetches a new CSRF (/api/csrf/) and updates _csrf and <meta name="wi-csrf">; /api/token/page/ stays only for compatibility. Server-only endpoints (deploy, cron, external triggers) pass allowPageToken: false to Handler::run(). api_activity keeps only ApiTokens::fingerprint(). See docs/app/piattaforma/api.md#token.
  • Keep runtime fallbacks and seed payloads separate:
    • Wonder\\App\\RuntimeDefaults is for runtime defaults used while rendering or composing in-memory config.
    • Wonder\\App\\SeedDefaults is for idempotent bootstrap/seed payloads consumed by build/row, setup commands, and seed-backed singleton forms.
  • Program in terms of future integration and extension. Prefer class and function designs that can be reused, extended, overridden, or integrated by sites and external modules instead of solving only the immediate local case.
  • Keep the architecture split clear:
    • Model::tableSchema() defines SQL structure
    • Model::dataSchema() defines data preparation and persistence behavior
    • Resource::formSchema() defines backend inputs
    • CustomPageSchema handles non-CRUD backend pages
    • Repeater handles repeatable rows and related-row sync
  • Form inputs go through the FormField hierarchy — always. Every input on the frontend Wonder theme (class/Themes/Wonder/) and the backend Bootstrap theme (class/Themes/Bootstrap/) is declared via FormField / RepeaterColumn (or directly through a typed Inputs\Input*) and rendered through Input::render($theme). No raw <input> / <select> / <textarea> HTML, no ad-hoc render functions. Missing input types are added at the framework layer (typed Inputs\Input* with element() → helper on FormField → renderer under each theme), not patched at the call site. Full rationale and the four-step "missing type" workflow in references/model-and-resource.md.
  • Wonder date renderers describe DatePicker, DateRange, DateTimeRange, and SelectDate with escaped data-wi-* attributes only. Their jQuery UI initialization belongs to wonder-image/lib/src/build/frontend/js/form/input.js through setInput(); never interpolate field values or bounds into renderer-owned inline scripts.
  • Translations go through Wonder\Localization: the Set-up Traduzioni page exports/imports a Catalog of TranslationSources (json, legal, model:<table>; modules add sources with localization.sources, sites with Catalog::set(), false removes); imports are previewed, stored server-side and never delete a key. Translatable Models implement Translatable + HasTranslations and read with Model::translated($rows, $lang); values live in translations with status automatic / reviewed / review, and only wonder.content-translation writes automatic ones (vocabulary, memory, Translator driver), never overwriting reviewed. legal_documents is the only legal source (LegalDocument::make($type)). Tests inject store and driver (Translations::useStore()), never call Google. See docs/app/concetti/traduzioni.md.
  • <wi-card> is retired. Do not add new <wi-card> tags and do not call wiCard() / wiCardLink() in new code: use Card in app, which renders a <div class="card">. When you edit a page that contains one, replace it. Do not add an option to Card to change its tag. Leave createCard() and the wi-card CSS rule in the lib: existing pages still depend on them until they are migrated.
  • The session CSRF token belongs to Wonder\Http\Csrf (token(), field(), verify(); field _csrf, header X-WI-CSRF, sent by the lib from wonder-image ≥ 2.1.2-alpha.31, <meta name="wi-csrf"> in the base layouts). Form renderers of both themes, Button::post(), POST Dropdown items, Modal::form() and the Resource form page emit the field on their own through Csrf::fieldFor(), never for GET and never without an active session; hand-written <form> tags add Csrf::field() themselves. Verification is per route: declare ->csrf() on the route or group and RouteDispatcher runs Wonder\Http\CsrfGuard after authorizeUser() (403 on a missing or wrong token); every /api/backend/* route except alert already has it, Bearer-only routes never do. Page handlers that write on POST call Csrf::verify() before writing and fail with alert 929, never with a text containing "CSRF" or "token" (helpers Csrf::ALERT, failureMessage(), oldInput(), flashFailure(), renderCsrfFailure()). Never act on GET. AuthSession delegates to Csrf; do not add new per-feature tokens (_contact_csrf was removed from the customer-account module, whose forms use _csrf; Impersonation keeps its scoped token). Details and the other security contracts: references/security.md.
  • Confirmations on shared components use the lib contract data-wi-confirm* (wi.confirm(), which returns Promise<boolean>), never window.confirm or hand-written onclick (from wonder-image lib ≥ 2.1.2-alpha.31). In the lib, the backend modal(text, link, …) is a @deprecated alias of wi.confirm(), while the frontend modal(selector) is @deprecated in favor of wi.modal.open(id, { title }) / wi.modal.close(id). The core PHP modal() (app/function/backend/modal.php) and the onclick="modal(...)" in plugin.php and Backend\Table\Field are removed on main (breaking app#129, not in a tag yet: v2.4.0-beta.5 still has them); legacy Delete buttons now print data-wi-confirm* and call ajaxRequest. New code never calls modal() or targets #modal. Use Button::confirm($message, title:, ok:, variant:) and Dropdown items with confirm, confirm_title, confirm_ok, confirm_variant. POST actions go on the <form> (Themes\Concerns\RendersPostForm, which also prints the CSRF field), the rest on the tag. A Dropdown item with 'method' => 'post' is a per-item CSRF form; action() is a <button type="button"> without href. Modal::form($action, $method, $hidden) wraps body and footer in a CSRF form with Cancel then Save (cancel(), submit(); a manual footer() replaces them) and must be rendered outside any form (it throws LogicException inside a Resource layout); rendered standalone in Bootstrap, columns() / columnSpan() lay out its fields as in a Resource layout. Date inputs read a slashed value as d/m/Y. Inner parts get one public method each (Modal::dialogClass()..footerClass(), Dropdown::toggleClass(), menuClass(), itemClass()) built on HasPartAttributes / RendersPartAttributes; classes append to the theme ones in a single class attribute. See docs/app/concetti/componenti/README.md.
  • A new component or public component API is not complete without its card in docs/components/<category>/<slug>.php; php tests/Docs/CatalogRenderTest.php must pass. Theme availability is computed by ThemeSupport, never written by hand. See references/component-catalogue.md.
  • Update docs/app/* in the same work when you change bootstrap, architecture, routing, layout structure, or developer-facing conventions.

Task Routing

Bootstrap or runtime

Read references/architecture.md, then inspect wonder-image.php, class/App/Credentials.php, route config, and the relevant registries. Be careful with anything that resolves ROOT, loads .env, or changes early bootstrap order.

CRUD resources, backend pages, or API generation

Read references/model-and-resource.md first for the Model / Resource extension contract, then references/architecture.md for registry precedence and route emission, then references/permissions-and-users.md for the permission keys referenced from Resource::permissionSchema().

Form inputs (frontend Wonder or backend Bootstrap)

Read references/model-and-resource.md — section "FormField hard rule". Every input on either theme is declared via FormField::key(...) / RepeaterColumn::key(...) or directly through a typed Inputs\Input*, then rendered through Input::render($theme). New input types are added under class/App/ResourceSchema/Inputs/, implement their own element(), expose a helper on FormField, and use renderers under class/Themes/Wonder/ and class/Themes/Bootstrap/ — not bespoke HTML at the call site.

UI / styling inside default components or themes

When the change touches a default component shipped by the framework (class/App/Resources/.../*.php views, the non-form Components under class/Elements/Components/, Media under class/Elements/Media/, the form Components under class/Elements/Form/Components/, the Wonder / Bootstrap renderers under class/Themes/{Wonder,Bootstrap}/, or any app/view/... page used by a site), the authoritative UI rulebook is wi-site/references/style-and-lib.md. The same reuse-first-from-wonder-image/lib policy applies inside wonder-image/app: do not invent new .wi-* names at framework level (that is a lib-side change), do not bake site-specific tokens into a default component, and preserve compatibility with the site's color.css / root.css. This includes shared action primitives like Button, Badge, ButtonGroup, and Dropdown. For link-like attributes on shared components, prefer the common concern class/Elements/Concerns/HasLinkAttributes.php and persist href, target, rel, title, onclick, download in attributes so theme renderers stay thin. Install wi-site alongside wi-app so this reference resolves locally.

Component cards and the catalogue

Read references/component-catalogue.md when adding or changing an Element in class/Elements/* or its public API: card location and format, validation test, how to open the catalogue, PageAssets::once() and the lib version constraint.

Security-sensitive code

Read references/security.md before touching request handling, sessions, cookies, uploads, exports, redirects, SQL helpers, logging or API auth. It names the owner class of each contract; reuse it instead of re-implementing the check in a handler or module.

Permissions, roles, or user management

Read references/permissions-and-users.md. Covers the builder API (Permissions::reset(), Area::make(), Permission::make()), the merge with module permissions, the runtime $PERMITS export, and the shared UserManagementResource / BackendUserResource / ApiUserResource flow.

Module system

Read references/architecture.md. Preserve compatibility with Composer discovery, filesystem fallback, enabled-module state, and route/model/resource registration from external packages.

Console commands or local-start flows

For scheduled tasks, cron entrypoints, task providers, retention, metrics, sitemap migration or the Google Places location sync (GoogleReviews), also read references/scheduler.md.

Read references/workflows.md. Remember these commands are meant to be executed from a site even when their source lives in this package.

Validation

  • Lint every touched PHP file with php -l.
  • Run composer dumpautoload when classes move or new classes are added.
  • If the change affects bootstrap, routing, console commands, resources, models, or request handlers, validate from a site with php forge update --local and php forge start.
  • If the change is Herd-specific, also validate the local routing path described in references/workflows.md.

Output Expectations

  • Explain whether the fix belongs in the framework, a site, or a module package.
  • Name the main files and registries involved instead of describing the framework generically.
  • Call out integration validation needs whenever a framework-root edit only becomes real through a site.

Comments

Markdown supported