chore: seal Design Vision v1 and monorepo scaffold

Archive the differentiated YuXinGu product docs, AI engineering system,
design contract, and Go/Vue scaffold. Next execution prioritizes Cece-parity
over early innovation (see .ai/product/STRATEGY.md).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-02 16:00:44 +08:00
co-authored by Cursor
parent 2686866376
commit 2fb1dfee14
193 changed files with 8854 additions and 1851 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"name": "@yuxingu/sdk",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@yuxingu/types": "0.1.0"
}
}
+75
View File
@@ -0,0 +1,75 @@
import type { ApiResponse } from '@yuxingu/types'
/** Platform adapters so H5 and mini-program share one client. */
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
path: string
body?: unknown
headers?: Record<string, string>
}
export interface ClientAdapters {
/** Perform HTTP and return parsed JSON envelope. */
request: <T>(opts: RequestOptions) => Promise<ApiResponse<T>>
getToken?: () => string | null | Promise<string | null>
}
export interface CreateClientOptions {
baseURL: string
adapters: ClientAdapters
}
/**
* createClient builds a typed API facade.
* Side effect: network via adapters.request.
*/
export function createClient(opts: CreateClientOptions) {
const { baseURL, adapters } = opts
async function call<T>(path: string, init?: Omit<RequestOptions, 'path'>): Promise<T> {
const token = adapters.getToken ? await adapters.getToken() : null
const headers: Record<string, string> = { ...(init?.headers || {}) }
if (token) headers.Authorization = `Bearer ${token}`
const res = await adapters.request<T>({
method: init?.method || 'GET',
path: joinURL(baseURL, path),
body: init?.body,
headers,
})
if (res.code !== 0) {
throw new Error(res.message || `api error ${res.code}`)
}
return res.data as T
}
return {
healthz: () => call<{ status: string }>('/api/v1/healthz'),
ping: () => call<{ pong: boolean }>('/api/v1/ping'),
}
}
/** Browser fetch adapter for user-h5. */
export function createBrowserAdapters(): ClientAdapters {
return {
request: async <T>({ method = 'GET', path, body, headers }) => {
const res = await fetch(path, {
method,
headers: {
'Content-Type': 'application/json',
...(headers || {}),
},
body: body === undefined ? undefined : JSON.stringify(body),
})
return (await res.json()) as ApiResponse<T>
},
getToken: () => localStorage.getItem('yxg_token'),
}
}
function joinURL(base: string, path: string): string {
if (path.startsWith('http')) return path
const b = base.replace(/\/$/, '')
const p = path.startsWith('/') ? path : `/${path}`
return `${b}${p}`
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@yuxingu/types",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
}
}
+25
View File
@@ -0,0 +1,25 @@
/** Unified API envelope from apps/api. */
export interface ApiResponse<T = unknown> {
code: number
message: string
data?: T
}
/** User profile used by decode / relation features. */
export interface Profile {
id: string
label: string
year: number
month: number
day: number
relation: 'self' | 'other'
}
/** Minimal scale list item. */
export interface ScaleSummary {
slug: string
name: string
category: string
questionCount: number
description?: string
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@yuxingu/ui",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/tokens.css",
"exports": {
"./tokens.css": "./src/tokens.css"
}
}
+79
View File
@@ -0,0 +1,79 @@
/* Shared design tokens — source of truth for H5 / mini-program / future clients
* Contract: .ai/design/design-system.md
*/
:root {
/* ── Semantic color (prefer in new UI) ── */
--color-primary: #e54d42;
--color-primary-hover: #d44338;
--color-primary-soft: #ffe4e4;
--color-primary-disabled: #f5b5b0;
--color-bg-start: #ffd1c7;
--color-bg-end: #ffc8b5;
--color-bg-sheet: #fff9f7;
--color-surface: #ffffff;
--color-text-primary: #333333;
--color-text-secondary: #999999;
--color-text-tertiary: #bbbbbb;
--color-border: #f0f0f0;
--color-border-strong: #eeeeee;
--color-input-bg: #fdfaf8;
--color-accent-gold: #c8923a;
--color-accent-gold-soft: #fff3d6;
--color-accent-blue: #4a90e2;
--color-accent-blue-soft: #e3eeff;
--color-accent-green: #5cb85c;
--color-accent-green-soft: #e0f6e4;
--color-accent-orange: #e8985a;
--color-accent-purple: #8b5cf6;
--color-success: #5cb85c;
--color-warning: #e8985a;
--color-error: #e54d42;
--color-info: #4a90e2;
/* ── Spacing ── */
--spacing-2xs: 4px;
--spacing-xs: 8px;
--spacing-sm: 12px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
/* ── Radius ── */
--radius-sm: 10px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--radius-pill: 999px;
/* ── Shadow / layout ── */
--shadow-card: 0 2px 10px rgba(0, 0, 0, 0.04);
--shadow-nav: 0 -2px 8px rgba(0, 0, 0, 0.03);
--layout-max-user: 430px;
--layout-nav-h: 56px;
/* ── Motion ── */
--duration-fast: 150ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
/* ── Legacy aliases (do not remove; migrate gradually) ── */
--yxg-pri: var(--color-primary);
--yxg-blue: var(--color-accent-blue);
--yxg-green: var(--color-accent-green);
--yxg-purple: var(--color-accent-purple);
--yxg-orange: var(--color-accent-orange);
--yxg-gold: var(--color-accent-gold);
--yxg-text: var(--color-text-primary);
--yxg-sub: var(--color-text-secondary);
--yxg-bg-start: var(--color-bg-start);
--yxg-bg-end: var(--color-bg-end);
--yxg-radius: var(--radius-lg);
--yxg-nav-h: var(--layout-nav-h);
--yxg-max-w: var(--layout-max-user);
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@yuxingu/utils",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
}
}
+20
View File
@@ -0,0 +1,20 @@
/** Clamp a number into [min, max]. */
export function clamp(n: number, min: number, max: number): number {
return Math.min(max, Math.max(min, n))
}
/** Pad date part to 2 digits. */
export function pad2(n: number): string {
return String(n).padStart(2, '0')
}
/**
* Validate calendar-ish birth inputs.
* Side effect: none. Returns error message or null if ok.
*/
export function validateBirth(year: number, month: number, day: number): string | null {
if (!Number.isFinite(year) || year < 1900 || year > 2100) return '年份无效'
if (month < 1 || month > 12) return '月份无效'
if (day < 1 || day > 31) return '日期无效'
return null
}