Skip to main content

webscheduler-frontend

WebScheduler frontend contract — Vite entry points, SPA lifecycle and navigation, dark mode (FOUC prevention + Tailwind class strategy), shared fetch layer, profile surface, avatar/initials system, design system (`xs-card`, `xs-btn`, `xs-actions-container`), `layouts/app` layout rules, and appointments toolbar layout. Use whenever you're working in `resources/js`, `resources/scss`, `app/Views/`, the SPA layer, Vite config, theme toggles, view layouts, avatars, or any styling/layout decision. Triggers on phrases like "view", "layout", "SPA", "Vite", "dark mode", "theme", "Tailwind", "SCSS", "avatar", "initials", "xs-card", "xs-btn", "appointments view", "toolbar", "scripts section", "DOMContentLoaded", "spa:navigated", or any JS/CSS/template change.

소스 정보

저장소
niloc95/xscheduler_ci4
최근 소스 활동
2026년 7월 21일 17:18
감지된 SKILL.md 언어
영어
스타
1
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
webscheduler-frontend
description
WebScheduler frontend contract — Vite entry points, SPA lifecycle and navigation, dark mode (FOUC prevention + Tailwind class strategy), shared fetch layer, profile surface, avatar/initials system, design system (`xs-card`, `xs-btn`, `xs-actions-container`), `layouts/app` layout rules, and appointments toolbar layout. Use whenever you're working in `resources/js`, `resources/scss`, `app/Views/`, the SPA layer, Vite config, theme toggles, view layouts, avatars, or any styling/layout decision. Triggers on phrases like "view", "layout", "SPA", "Vite", "dark mode", "theme", "Tailwind", "SCSS", "avatar", "initials", "xs-card", "xs-btn", "appointments view", "toolbar", "scripts section", "DOMContentLoaded", "spa:navigated", or any JS/CSS/template change.
# WebScheduler — Frontend Contract ## 1. Canonical Entry Points (Vite) Defined in `vite.config.js` `rollupOptions.input`: | Key | File | | --- | --- | | `main` | `resources/js/app.js` | | `style` | `resources/scss/app-consolidated.scss` | | `spa` | `resources/js/spa.js` | | `dark-mode` | `resources/js/dark-mode.js` | | `theme-bootstrap` | `resources/js/theme-bootstrap.js` | | `app-layout-init` | `resources/js/layout/app-layout-init.js` | | `public-booking` | `resources/js/public-booking.js` | | `public-booking-bootstrap` | `resources/js/public-booking-bootstrap.js` | | `unified-sidebar` | `resources/js/unified-sidebar.js` | | `charts` | `resources/js/charts.js` | | `setup` | `resources/js/setup.js` | `resources/js/material-web.js` was removed — the `@material/web` bundle was unused (zero `<md-*>` elements in production views). **Non-entry utility modules** (import only, not Vite entry points): - `resources/js/currency.js` — currency formatting, loads settings from `/api/v1/settings/localization` - `resources/js/utils/avatar.js` — avatar initials helper (see §6 below) ## 2. Lifecycle Contract Core lifecycle for authenticated surfaces: 1. `app.js` loads 2. `spa.js` intercepts links/forms 3. `initializeComponents()` runs on load and after `spa:navigated` 4. View code uses `xsRegisterViewInit(fn)` 5. Initializers **must be idempotent** via dataset guards Body-level overlays or modals created by JS (outside `#spa-content`) must explicitly clean themselves up on `spa:leaving`. Hiding them is not sufficient when the DOM node survives SPA swaps; teardown must remove stale nodes, listeners, and pending timers so the next initializer run can recreate fresh state. **Do not add bare `DOMContentLoaded`-only logic for app surfaces that can SPA-navigate.** ## 3. SPA Navigation Contract `spa.js` behavior: - Swaps `#spa-content` - Emits `spa:navigated` after successful swap - Supports `data-no-spa` and `no-spa` opt-outs - Supports `forceReload` semantics for same-URL post-mutation refresh If a controller action redirects back to the current page, the JSON response **must** include a `redirect` key so SPA navigation can force-reload (see `api-contract` skill). ## 4. Dark Mode Contract (Owner Section) **Tailwind configuration:** `darkMode: 'class'` in `tailwind.config.js`. All Tailwind `dark:` utilities require `.dark` on `<html>`. **FOUC prevention — inline blocking script:** The script is **literally inlined** in `<head>` — NOT a `<script src>` or `type="module"`. All three layouts (`app.php`, `public.php`, `auth.php`) and the standalone `booking.php` carry the same one-liner. It runs synchronously during HTML parsing, before any CSS file loads: ```html <script {csp-script-nonce}>!function(){var t=localStorage.getItem('xs-theme')||(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');document.documentElement.setAttribute('data-theme',t);document.documentElement.classList.toggle('dark',t==='dark');document.documentElement.style.colorScheme=t;if(t==='dark')document.documentElement.style.backgroundColor='#111827';document.documentElement.classList.add('xs-no-transition')}();</script> ``` > **CSP nonce required:** Every inline `<script>` in CI4 layouts must carry `{csp-script-nonce}`. CI4 replaces this placeholder with `nonce="…"` when `app.CSPEnabled = true`. Omitting it causes a CSP violation and the script is silently blocked in production. What it does (in order): 1. Reads `xs-theme` from `localStorage`; falls back to `prefers-color-scheme` 2. Sets `data-theme` attribute + toggles `.dark` class on `<html>` 3. Sets `style.colorScheme = t` — system UI (scrollbars, native inputs) renders in the right scheme 4. For dark mode: sets `style.backgroundColor = '#111827'` on `<html>` — covers viewport before CSS loads 5. Adds `.xs-no-transition` class — suppresses all `transition-colors` when the CSS file first applies **`xs-no-transition` CSS rule** lives in `resources/scss/base/_reset.scss`: ```scss .xs-no-transition, .xs-no-transition * { transition: none !important; } ``` This prevents the 200ms animated fade that would otherwise occur when `dark:bg-gray-900` first applies to `<body>`. **FOUC cleanup:** The guards must be removed after the first paint to restore normal transitions for user-triggered theme switches. - **Layouts that load `app-layout-init.js`:** cleanup runs in the double `requestAnimationFrame` inside `onDomReady()`. - **Standalone pages (e.g. `booking.php`):** cleanup is a bare inline script at end of `<body>`: ```html <script {csp-script-nonce}>requestAnimationFrame(function(){requestAnimationFrame(function(){document.documentElement.classList.remove('xs-no-transition');document.documentElement.style.backgroundColor='';document.documentElement.style.colorScheme=''})});</script> ``` **Dark background values by page:** - Authenticated app (`layouts/app.php`, `auth.php`, `public.php`): `#111827` (Tailwind `gray-900` = `dark:bg-gray-900` on `<body>`) - Public booking SPA (`booking.php`): `#0f172a` (Tailwind `slate-900` = `dark:bg-slate-900` on `<body>`) **`<html>` tag:** must NOT carry `transition-colors duration-200` — it has no `background-color` rule. `transition-colors` lives on `<body>` only, for user-triggered theme switches. **`theme-bootstrap` Vite entry:** still exists in `vite.config.js` and builds `resources/js/theme-bootstrap.js`, but **no layout loads it via `<script src>`**. The content is inlined directly. Do not re-introduce the module src load — it would defer execution and break FOUC prevention. **Runtime management:** `DarkModeManager` in `resources/js/dark-mode.js` handles the full lifecycle: - On every `applyTheme()` call: sets `data-theme` attribute, `.dark` class, AND `style.colorScheme` - Persists to `localStorage` key `xs-theme` - Dispatches `xs:theme-changed` custom event for downstream components - Re-wires `[data-theme-toggle]` buttons after each `spa:navigated` event **SCSS:** Dark overrides use `.dark { ... }` selector in `resources/scss/abstracts/_custom-properties.scss`, **not** `[data-theme="dark"]`. **Summary:** `data-theme` attribute is always kept in sync (for external consumers), but `.dark` class is the canonical trigger for Tailwind utilities and SCSS dark overrides. See `ui-ux` skill for dark mode styling rules (color tokens, xs-no-transition usage). ## 5. Styling Contract - Tailwind + consolidated SCSS only - **No inline `style` attributes** in app-facing templates — caught by pre-merge grep check ## 6. Avatar System Contract (Owner Section) All avatar rendering — profile images and initials fallback — has a **single source of truth** on both PHP and JS sides. ### 6.1 PHP Helpers (`app/Helpers/app_helper.php`) Four canonical helpers, loaded via `helper('app')`: | Helper | Purpose | | --- | --- | | `avatar_initials(string $name, string $default = 'U'): string` | Derives 1-2 letter initials from a display name. Strips titles (Dr., Prof., Mr., Mrs., Ms., Rev.) and suffixes (MD, PhD, DDS, DO, RN, NP, PA, DVM, Jr., Sr., II, III, IV). Multi-word → first + last initial. Single-word → first 2 chars. Empty → `$default`. | | `avatar_display_name(array $user, string $fallback = 'User'): string` | Prefers `$user['name']`; falls back to `first_name` + `last_name` concatenation. | | `avatar_profile_image_url(array $user): ?string` | Resolves profile image URL. Paths starting with `assets/` map to `FCPATH`; paths starting with `uploads/` or `writable/` map to `WRITEPATH`. Returns `null` if no usable path. | | `avatar_data(array $user, string $defaultInitial = 'U'): array` | Returns `['imageUrl' => ?string, 'initials' => string, 'displayName' => string]`. Use this in views for image-first rendering with initials fallback. | `ProfilePageService::buildProfileImageUrl()` and `buildProfileInitials()` delegate to these helpers. ### 6.2 JS Utility (`resources/js/utils/avatar.js`) Single ESM module — import directly: ```js import { getAvatarInitials, getDisplayName } from '../../utils/avatar.js'; ``` | Export | Signature | Behaviour | | --- | --- | --- | | `getDisplayName(entity, fallback)` | `(object, string) → string` | Prefers `entity.name`; falls back to `first_name` + `last_name`. | | `getAvatarInitials(name, options)` | `(string, { defaultInitial? }) → string` | Same normalization rules as PHP: strip titles/suffixes, multi-word → first+last initial, single-word → first 2 chars. `options.defaultInitial` defaults to `'U'`. | For inline `<script>` blocks in PHP views (which cannot use ESM imports), use the globals exposed by `resources/js/app.js`: ```js window.xsGetAvatarInitials(name, defaultInitial) window.xsGetDisplayName(entity, fallback) ``` ### 6.3 Canonical Default Initials by Context | Context | Default | | --- | --- | | User / staff / header | `'U'` | | Customer | `'C'` | | Staff assignment widget | `'S'` | | Provider assignment widget | `'P'` | | Scheduler provider chip | `'?'` | ### 6.4 Covered Surfaces - `app/Views/layouts/app.php` — header user avatar (image-first via `avatar_data()`) - `app/Views/user-management/index.php` — PHP rows + JS `userRow()` via `window.xsGetAvatarInitials` - `app/Views/customer-management/index.php` — `avatar_data()` with `defaultInitial: 'C'` - `app/Views/customer-management/history.php` — large customer header avatar - `app/Views/appointments/form.php` — customer avatar placeholder (`'C'`) - `app/Views/user-management/components/provider-staff.php` — server PHP + JS `renderStaff()` widget - `app/Views/user-management/components/staff-providers.php` — server PHP + JS `renderProviders()` widget - `resources/js/modules/scheduler/appointment-colors.js` — `getProviderInitials()` delegates to `getAvatarInitials` - `resources/js/modules/customer-management/customer-search.js` - `resources/js/modules/appointments/appointments-form.js` ### 6.5 Do Not Duplicate **Do not reimplement initials logic in any view, controller, or JS module.** Always call the shared helper. A one-letter initial in a completed surface is a regression against this contract. ## 7. View Layout & Design System Contract > **Design system component tokens (`xs-card`, `xs-btn`, `xs-actions-container`) are owned by the `ui-ux` skill.** This section covers layout-level contracts only. ### 7.1 Canonical Layout All authenticated views **must** extend `layouts/app`. The legacy `layouts/dashboard` layout is **deprecated**; do not use it for new views or when migrating existing views. | Layout | Status | Sections | | --- | --- | --- | | `layouts/app` | **Active standard** | `sidebar`, `header_title`, `header_subtitle`, `header_primary_action`, `header_controls`, `content`, `scripts`, `modals` | | `layouts/dashboard` | **Deprecated** | `dashboard_stats`, `dashboard_actions`, `dashboard_filters`, `dashboard_content_top`, `dashboard_content` | To suppress the header's built-in "New Appointment" CTA for a page that provides its own CTA in `content`: ```php <?= $this->section('header_primary_action') ?>hidden<?= $this->endSection() ?> ``` ### 7.2 Design System Component Classes | Token | Element | Notes | | --- | --- | --- | | `xs-card` | Card container | Replaces `card card-spacious` | | `xs-card-header` | Card header bar | Use with `xs-card-header-content` (left) + `xs-card-actions` (right) | | `xs-card-header-content` | Left slot of card header | Contains title + subtitle | | `xs-card-title` | Title inside `xs-card-header-content` | | | `xs-card-subtitle` | Subtitle inside `xs-card-header-content` | | | `xs-card-actions` | Right slot of card header | Icon buttons, secondary controls | | `xs-card-body` | Card content area | Add `p-0` when the card contains a full-bleed table |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기