create-lupine 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +3 -1
- package/package.json +1 -1
- package/templates/common/AI_CONTEXT.md +210 -0
- package/templates/doc-starter/web/github-pj-name/index.tsx +0 -2
- package/templates/doc-starter/web/src/index.tsx +1 -3
- package/templates/hello-world/web/src/index.tsx +4 -0
- package/templates/hello-world/web/src/styles/app.css +0 -0
- package/templates/hello-world/web/src/styles/base-css.ts +1 -0
- package/templates/hello-world/web/src/styles/global.css +2075 -0
- package/templates/hello-world/web/src/styles/theme.ts +16 -0
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -190,6 +190,8 @@ async function init() {
|
|
|
190
190
|
const templateObj = TEMPLATES.find((t) => t.name === template);
|
|
191
191
|
if (templateObj && templateObj.needsPress) {
|
|
192
192
|
pkg.dependencies['lupine.press'] = '^1.0.1';
|
|
193
|
+
pkg.devDependencies['gray-matter'] = '^4.0.3';
|
|
194
|
+
pkg.devDependencies['marked'] = '^17.0.1';
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2));
|
package/package.json
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# AI Context for Lupine.js
|
|
2
|
+
|
|
3
|
+
**SYSTEM ROLE**: You are an expert developer in `lupine.js`, a custom TypeScript full-stack framework.
|
|
4
|
+
|
|
5
|
+
**🛑 CRITICAL WARNINGS 🛑**
|
|
6
|
+
|
|
7
|
+
1. **NO REACT HOOKS**: `useState`, `useEffect`, `useReducer`, `useCallback`, `useContext` **DO NOT EXIST**.
|
|
8
|
+
2. **NO VIRTUAL DOM STATE**: Changing a variable DOES NOT re-render the component. You must manually update `HtmlVar.value`.
|
|
9
|
+
3. **NO CONTROLLED INPUTS**: Do not bind `value={state}`. Read values from DOM on submit.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Core Philosophy & Reactivity
|
|
14
|
+
|
|
15
|
+
- **`HtmlVar` is the "State"**:
|
|
16
|
+
- Use `HtmlVar` to wrap dynamic sections (lists, conditional renderings, async content).
|
|
17
|
+
- **Pattern**: `const dom = new HtmlVar(initialContent);` -> JSX `{dom.node}` -> `dom.value = updatedContent`.
|
|
18
|
+
- **Direct DOM Access**:
|
|
19
|
+
- Use `RefProps` to get reference to the component root.
|
|
20
|
+
- Use `ref.$(selector)` to find elements (inputs, containers).
|
|
21
|
+
- **Value Retrieval**: `const val = ref.$('input.my-class').value`.
|
|
22
|
+
|
|
23
|
+
## 2. Key Interfaces
|
|
24
|
+
|
|
25
|
+
### `RefProps` (Lifecycle & DOM)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const ref: RefProps = {
|
|
29
|
+
// Mounted: Initialize data, timers, events
|
|
30
|
+
onLoad: async (el: Element) => {
|
|
31
|
+
await loadData();
|
|
32
|
+
// ref.$('.sub-element').addEventListener(...)
|
|
33
|
+
},
|
|
34
|
+
// Unmounting: Cleanup
|
|
35
|
+
onUnload: async (el: Element) => {
|
|
36
|
+
// Cleanup (timers, sockets)
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
// Usage
|
|
40
|
+
<div ref={ref}>...</div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `CssProps` (Styling)
|
|
44
|
+
|
|
45
|
+
Supports nesting and media queries. **Prefer this over inline styles.**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { MediaQueryRange } from "lupine.components";
|
|
49
|
+
|
|
50
|
+
const css: CssProps = {
|
|
51
|
+
display: "flex",
|
|
52
|
+
".child": { color: "var(--primary-color)" },
|
|
53
|
+
// Responsive
|
|
54
|
+
[MediaQueryRange.MobileBelow]: {
|
|
55
|
+
flexDirection: "column",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 3. Styles & Themes ("The Look")
|
|
61
|
+
|
|
62
|
+
### Global Variables (Theming)
|
|
63
|
+
|
|
64
|
+
**NEVER hardcode colors** (e.g., `#000`). Always use CSS variables to support Dark/Light modes.
|
|
65
|
+
|
|
66
|
+
- **Colors**: `var(--primary-color)`, `var(--primary-bg-color)`, `var(--secondary-color)`, `var(--error-color)`.
|
|
67
|
+
- **Borders**: `var(--primary-border)`, `var(--border-radius-m)`.
|
|
68
|
+
- **Spacing**: `var(--space-m)` (8px), `var(--space-l)` (16px).
|
|
69
|
+
|
|
70
|
+
### Standard Utility Classes
|
|
71
|
+
|
|
72
|
+
- **Flexbox**: `.row-box` (flex row, align-center), `.col` (flex: 1).
|
|
73
|
+
- **Margins/Padding**: `m-auto`, `p-m`, `mt-s`, `pb-l` (s=small, m=medium, l=large).
|
|
74
|
+
- **Text**: `.text-center`, `.ellipsis`.
|
|
75
|
+
|
|
76
|
+
### Standard UI Components
|
|
77
|
+
|
|
78
|
+
#### Settings Group (Mobile/Desktop Settings)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<div class='setting-section-group'>
|
|
82
|
+
<div class='setting-section-title'>Section Title</div>
|
|
83
|
+
<div class='setting-section-block'>
|
|
84
|
+
<div class='setting-section-item' onClick={...}>
|
|
85
|
+
<div class='setting-section-item-text'>My Option</div>
|
|
86
|
+
<div class='setting-section-item-icon'><i class='ifc-icon ma-chevron-right'></i></div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Admin Edit Row (Form Layout)
|
|
93
|
+
|
|
94
|
+
Standard label + input row pattern for admin pages.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<div class="row-box mt-m">
|
|
98
|
+
<div class="label-class">Title: </div>
|
|
99
|
+
<div class="flex-1">
|
|
100
|
+
{/* width-100p ensures input fills the flex-1 container */}
|
|
101
|
+
<input
|
|
102
|
+
type="text"
|
|
103
|
+
class="input-base my-input-class w-100p"
|
|
104
|
+
value={item.title}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 4. Common Patterns ("The Lupine Way")
|
|
111
|
+
|
|
112
|
+
### List / Search (No Re-render)
|
|
113
|
+
|
|
114
|
+
**Pattern**: Create a render function (`makeList`) and assign its result to `HtmlVar`.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const MyPage = () => {
|
|
118
|
+
// 1. Logic Variables (Not State)
|
|
119
|
+
let pageIndex = 0;
|
|
120
|
+
|
|
121
|
+
// 2. Dynamic Container
|
|
122
|
+
const listDom = new HtmlVar(<div>Loading...</div>);
|
|
123
|
+
|
|
124
|
+
// 3. Render Function
|
|
125
|
+
const makeList = async () => {
|
|
126
|
+
const data = await fetchData(pageIndex);
|
|
127
|
+
return <div>{data.map(item => <Item item={item} />)}</div>;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 4. Events
|
|
131
|
+
const onSearch = async () => {
|
|
132
|
+
// Read directly from DOM
|
|
133
|
+
const query = ref.$('input.search').value;
|
|
134
|
+
// Update logic var
|
|
135
|
+
pageIndex = 0;
|
|
136
|
+
// Update UI manually
|
|
137
|
+
listDom.value = await makeList();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const ref: RefProps = {
|
|
141
|
+
onLoad: async () => {
|
|
142
|
+
listDom.value = await makeList();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div ref={ref}>
|
|
148
|
+
<input class="search" />
|
|
149
|
+
<button onClick={onSearch}>Go</button>
|
|
150
|
+
{/* Embed Dynamic Content */}
|
|
151
|
+
{listDom.node}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Mobile Navigation (`SliderFrame`)
|
|
158
|
+
|
|
159
|
+
Lupine uses a "Slide-over" model for navigation (Drill-down).
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { SliderFrame, SliderFrameHookProps, HeaderWithBackFrame } from 'lupine.components';
|
|
163
|
+
|
|
164
|
+
// Parent Component
|
|
165
|
+
const Parent = () => {
|
|
166
|
+
const sliderHook: SliderFrameHookProps = {};
|
|
167
|
+
|
|
168
|
+
const openDetail = (id) => {
|
|
169
|
+
// Push new view onto stack
|
|
170
|
+
sliderHook.load!(<DetailComponent id={id} sliderFrameHook={sliderHook} />);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div>
|
|
175
|
+
<SliderFrame hook={sliderHook} />
|
|
176
|
+
<div onClick={() => openDetail(1)}>Click Me</div>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Child Component
|
|
182
|
+
const DetailComponent = (props) => {
|
|
183
|
+
return (
|
|
184
|
+
<HeaderWithBackFrame
|
|
185
|
+
title="Detail Page"
|
|
186
|
+
onBack={(e) => props.sliderFrameHook.close!(e)}
|
|
187
|
+
>
|
|
188
|
+
Content...
|
|
189
|
+
</HeaderWithBackFrame>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 5. Architecture Cheat Sheet
|
|
195
|
+
|
|
196
|
+
- **`lupine.api` (Backend)**:
|
|
197
|
+
- `req.locals.json()` to get body.
|
|
198
|
+
- `apiCache.getDb().selectObject('$__table', ...)`
|
|
199
|
+
- `ApiHelper.sendJson(req, res, { status: 'ok' })`
|
|
200
|
+
- **`lupine.web` (Frontend)**:
|
|
201
|
+
- `NotificationMessage.sendMessage('Msg', NotificationColor.Success)`
|
|
202
|
+
- `getRenderPageProps().renderPageFunctions.fetchData('/api/...')`
|
|
203
|
+
|
|
204
|
+
## 6. Coding Standards & Gotchas
|
|
205
|
+
|
|
206
|
+
- **❌ React Hooks**: `useState`, `useEffect` **DO NOT EXIST**. Use `HtmlVar` and `RefProps`.
|
|
207
|
+
- **❌ `className`**: Use standard HTML `class`.
|
|
208
|
+
- **⚠️ `style={{}}`**: **Allowed** for simple or dynamic inline styles (e.g., `style={{ border: '1px solid red' }}`), but **prefer `css={CssProps}`** for structural/theme styling.
|
|
209
|
+
- **✅ Native Events**: `onClick`, `onChange`, `onInput`, `onMouseMove` etc. are standard HTML events and **ARE ALLOWED**. Use them for triggering logic or callbacks (e.g., `onInput={(e) => updateOtherThing(e.target.value)}`).
|
|
210
|
+
- **✅ Uncontrolled Inputs**: While you _can_ use `onInput` to track state, the default efficient pattern is often to read `ref.$('input').value` only when the user clicks "Save" or "Search".
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
isFrontEnd,
|
|
8
8
|
debugWatch,
|
|
9
9
|
webEnv,
|
|
10
|
-
setDefaultMetaDescription,
|
|
11
10
|
bindGlobalStyle,
|
|
12
11
|
} from 'lupine.components';
|
|
13
12
|
import { bindPressData, PressPage, pressThemes, setPressSubDir } from 'lupine.press';
|
|
@@ -23,7 +22,6 @@ bindLang('en', {});
|
|
|
23
22
|
bindTheme('light', pressThemes);
|
|
24
23
|
bindGlobalStyle('comm-css', baseCss, false, true);
|
|
25
24
|
setDefaultPageTitle('Doc Starter');
|
|
26
|
-
setDefaultMetaDescription('Doc Starter Demo');
|
|
27
25
|
|
|
28
26
|
bindPressData(markdownConfig);
|
|
29
27
|
setPressSubDir('/github-pj-name');
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
isFrontEnd,
|
|
8
8
|
debugWatch,
|
|
9
9
|
webEnv,
|
|
10
|
-
setDefaultMetaDescription,
|
|
11
10
|
bindGlobalStyle,
|
|
12
11
|
} from 'lupine.components';
|
|
13
12
|
import { bindPressData, PressPage, pressThemes } from 'lupine.press';
|
|
@@ -22,8 +21,7 @@ if (isFrontEnd() && webEnv(ClientEnvKeys.NODE_ENV, '') === 'development') {
|
|
|
22
21
|
bindLang('en', {});
|
|
23
22
|
bindTheme('light', pressThemes);
|
|
24
23
|
bindGlobalStyle('comm-css', baseCss, false, true);
|
|
25
|
-
setDefaultPageTitle('
|
|
26
|
-
setDefaultMetaDescription('LupineJS Doc');
|
|
24
|
+
setDefaultPageTitle('Lupine.js Doc');
|
|
27
25
|
|
|
28
26
|
bindPressData(markdownConfig);
|
|
29
27
|
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const baseCss = {};
|