Skip to content

Kupola Development Standards

Core Architecture

Kupola 2.0 is a zero-dependency UI framework with:

  • Signal-based reactivity: signal, computed, effect
  • Template literals: html tagged template + render()
  • Declarative directives: k-data, k-show, k-bind, k-on, k-model, k-for
  • SSR support: renderToString + hydrate

Component Pattern

All components follow the factory function pattern:

javascript
export function ComponentName(options = {}) {
  const { /* destructured options */ } = options;
  
  // Create DOM structure
  const element = document.createElement('div');
  element.className = 'kupola-componentname';
  
  // Reactive state
  const state = signal(initialValue);
  
  // Render
  const update = () => {
    element.innerHTML = ''; // build UI
  };
  effect(update);
  
  // Public API
  return {
    element,
    destroy() { /* cleanup */ },
    // ... other methods
  };
}

CSS Class Naming

All CSS classes use kupola- prefix:

css
/* ✅ Correct */
.kupola-modal { }
.kupola-modal-overlay { }
.kupola-modal-header { }

/* ❌ Wrong */
.modal { }
.ds-modal { }
.kupolaModal { }

Reactivity Rules

Signal Usage

javascript
// ✅ Correct - read with .value
const count = signal(0);
console.log(count.value);

// ❌ Wrong - calling signal as function
console.log(count());

// ✅ Correct - write with .value
count.value = 5;
count.value++;

Computed & Effect

javascript
// Computed - derived value, auto-tracks dependencies
const doubled = computed(() => count.value * 2);

// Effect - side effect, auto re-runs when dependencies change
effect(() => {
  console.log(`Count: ${count.value}, Doubled: ${doubled.value}`);
});

Batch Updates

javascript
// Merge multiple updates into one flush
batch(() => {
  count.value++;
  name.value = 'new';
});

Template & Render

javascript
import { signal } from '@kupola/core';
import { html, render } from '@kupola/platform';

const count = signal(0);

const view = () => html`
  <div class="kupola-counter">
    <p>Count: ${count}</p>
    <button @click=${() => count.value++}>+1</button>
  </div>
`;

render(view(), document.getElementById('app'));

Directive System (Declarative HTML)

html
<div k-data="{ count: 0, name: '' }">
  <input k-model="name" placeholder="Name">
  <p k-show="name">Hello, {{ name }}!</p>
  <button k-on:click="count++">Clicked {{ count }}</button>
  <span k-bind:class="count > 10 ? 'highlight' : ''">Status</span>
  <ul>
    <li k-for="item in items" k-text="item.name"></li>
  </ul>
</div>
DirectiveShorthandPurpose
k-dataCreate reactive scope
k-showConditional display
k-textReactive textContent
k-htmlReactive innerHTML
k-bind:Dynamic attribute
k-on@Event listener
k-modelTwo-way binding
k-forList rendering
k-router-linkRouter link navigation
k-router-viewRouter view container
k-permissionPermission-based visibility

Import Paths

javascript
// Core engine
import { signal } from '@kupola/core';
import { html, render } from '@kupola/platform';

// Components - each independently bundled
import { Modal } from '@kupola/components/modal';
import { Table } from '@kupola/components/table';
import { Dropdown } from '@kupola/components/dropdown';

// SSR
import { renderToString, hydrate } from '@kupola/platform/server';

// Directives
import { walk } from '@kupola/platform/directives';

// Theme (anti-FOUC)
import { themePreload, setTheme, toggleTheme, getPreferredTheme, onThemeChange, getThemeInlineScript } from '@kupola/platform';

// Lazy loading
import { lazyComponent, preloadComponent } from '@kupola/platform';

// DevTools
import { enableProfiler, getProfileReport } from '@kupola/core';

// i18n
import { setLocale, getLocale, t, addMessages } from '@kupola/platform';

// Router
import { createRouter, useRouter, useRoute, installRouter } from '@kupola/router';
import { registerRouterLinkDirective, registerRouterViewDirective } from '@kupola/router';
import { setupAuthGuard } from '@kupola/router/auth';
import { matchRouteServer, createServerRouter } from '@kupola/router/server';

// Auth
import { createAuthContext, hydrateAuthContext, getAuthContext } from '@kupola/auth';
import { registerPermissionHandler, getPermissionHandler } from '@kupola/auth';
import { setupHttpGuard } from '@kupola/auth/http';
import { requireAuth, requirePermission, requireRole } from '@kupola/auth';

// CSS
import '@kupola/platform/css';              // full bundle
import '@kupola/platform/css/tokens';        // tokens only
import '@kupola/platform/css/components';    // components only
import '@kupola/platform/css/responsive';     // responsive utilities

Theme System (Anti-FOUC)

javascript
// Blocking preload — call in <head> before first paint
themePreload(); // reads localStorage('kupola-theme') + prefers-color-scheme, sets data-theme, removes [k-cloak]

// Programmatic control
setTheme('dark');        // set + persist to localStorage
toggleTheme();           // toggle dark ↔ light
getPreferredTheme();     // returns 'light' | 'dark'
onThemeChange(theme => { /* callback */ });
getThemeInlineScript();  // returns <script> string for SSR injection

CSS: [k-cloak] { display: none !important; } — hides elements until JS removes the attribute.

Responsive Breakpoints

BreakpointValueDevice
sm576pxPhone landscape
md768pxTablet
lg1024pxLaptop
xl1280pxDesktop
html
<!-- Display utilities -->
<div class="ds-hide-sm">Hidden on phones</div>
<div class="ds-show-md">Only visible on tablets+</div>

<!-- Component auto-adaptations (< 576px) -->
<!-- Modal → fullscreen, Drawer → full-width, Table → horizontal scroll, Select → bottom sheet -->

Anti-Patterns

❌ Don't✅ Do Instead
element.style.display = 'none'Use k-show or toggle class
document.querySelector('.my-class')Use component.element.querySelector()
count() to read signalcount.value
count = 5 to write signalcount.value = 5
Raw innerHTML = ...Use html template + render()
Direct DOM manipulation in effectsLet template handle DOM updates
Hardcode data-theme without preloadUse themePreload() or inline script in <head>

Adding a New Component

When creating a new component, update these files:

  1. Source: packages/core/src/components/{name}.js
  2. Test: packages/core/__tests__/components/{name}.test.js
  3. Build entry: rollup.config.cjs — add input entry
  4. Exports: packages/core/package.json — add to exports
  5. Size limit: .size-limit.json — add limit entry
  6. Types: packages/core/src/components/types.d.ts — add interfaces

File Structure

packages/core/
├── src/
│   ├── components/     # UI components (one file each)
│   │   ├── modal.js
│   │   ├── table.js
│   │   └── types.d.ts  # TypeScript definitions
│   ├── signal.js       # Signal primitive
│   ├── computed.js     # Computed values
│   ├── effect.js       # Effect system
│   ├── template.js     # html`` template
│   ├── render.js       # DOM renderer
│   ├── server.js       # SSR (renderToString + hydrate)
│   ├── directives.js   # k-* directive system
│   ├── theme.js        # Theme utilities (anti-FOUC)
│   ├── lazy.js         # Lazy component loading
│   ├── devtools.js     # Signal profiler
│   ├── i18n.js         # Internationalization
│   ├── errors.js       # ErrorBoundary
│   └── index.js        # Public API entry
└── __tests__/
    └── components/     # Component tests

Testing

bash
npm run test          # Run all 922 tests
npm run test:watch    # Watch mode
npm run test:coverage # Coverage report

Tests use Jest with jsdom. Test file pattern: {component}.test.js


Token Efficiency

Avoid wasting tokens/credits:

✅ Do

  • Search first: Use grep/search to locate relevant code before reading files
  • Read specific ranges: Read(file, start_line, end_line) instead of entire files
  • One component at a time: Only load the component being worked on
  • Skip verification if confident: Don't re-run tests if change is trivial (typo fix, comment)
  • Batch related changes: Group multiple edits to same file in one operation

❌ Avoid

  • Reading all 48 component files "to understand the codebase"
  • Re-reading files already in context
  • Running full test suite after every single-line change
  • Generating verbose explanations when user just wants code
  • Loading README/CONTRIBUTING/INTEGRATION unless explicitly asked

Quick Reference Paths

NeedRead
Component sourcepackages/core/src/components/{name}.js
Component testpackages/core/__tests__/components/{name}.test.js
Build configrollup.config.cjs (line ~1600-1800 for component entries)
Type definitionspackages/core/src/components/types.d.ts
Core APIpackages/core/src/index.js (55 lines)
Theme APIpackages/core/src/theme.js
Responsive CSSpackages/css/responsive.css
CSS buildscripts/build-css.cjs

文件

见仓库根目录 SKILL.md