Skip to content

Commit 1fc6eff

Browse files
authored
Merge pull request #648 from objectstack-ai/copilot/complete-dx-roadmap-development
2 parents 1d3d2ed + 61ae67f commit 1fc6eff

83 files changed

Lines changed: 9175 additions & 2600 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursorrules

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ This repository contains NO database connections, NO UI components, and NO runti
66
* TypeScript Interfaces (Shared types).
77
* JSON Schemas / Zod Schemas (Validation rules).
88
* Constants (Convention configurations).
9+
10+
⚠️ CRITICAL: Documentation Directory Conventions
11+
* `content/docs/references/` — AUTO-GENERATED by `packages/spec/scripts/build-docs.ts`.
12+
DO NOT place hand-written content here. The script deletes and regenerates all
13+
category sub-folders on every build. Any manually added files WILL be lost.
14+
* `content/docs/guides/` — HAND-WRITTEN documentation. This is where curated
15+
references (field type gallery, error catalog, cheat sheets, contracts docs,
16+
decision trees, wire format examples, permissions matrix, etc.) belong.
17+
* If you need to add new documentation, always put it in `content/docs/guides/`
18+
and update `content/docs/guides/meta.json` to include the new page.
919
1. The "Manifest" Standard (Core Responsibility)
1020
You define what a "Package" looks like in ObjectStack.
1121
* Schema Location: src/schemas/manifest.zod.ts (Export to JSON Schema).

.cursorrules.d/objectstack.prompt.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ Directory Structure & Responsibilities
99
* packages/spec (The Constitution) [Apache 2.0]
1010
* CRITICAL: Contains the shared manifest.schema.json, TypeScript interfaces, and plugin lifecycle hooks (onInstall, onEnable).
1111
* Rule: All other packages depend on this. No circular dependencies.
12+
* content/docs/references/ — ⚠️ AUTO-GENERATED by packages/spec/scripts/build-docs.ts.
13+
* DO NOT place hand-written content here. The build script deletes and regenerates
14+
all category sub-folders. Any manually added files WILL be destroyed.
15+
* content/docs/guides/ — HAND-WRITTEN documentation.
16+
* Curated references, tutorials, cheat sheets, and all manually authored docs belong here.
17+
* Update content/docs/guides/meta.json when adding new pages.
1218
* packages/objectql (Data Engine) [Apache 2.0]
1319
* Universal Data Protocol. Compiles GraphQL-like queries into SQL/Redis commands.
1420
* packages/objectos (Business Kernel) [AGPL v3]

DX_ROADMAP.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ This roadmap prioritizes improvements based on the **"Time to First Wow"** metri
9292
- [x] Add "How to Run" section to each example README (app-todo, app-crm, app-host, plugin-bi)
9393
- [x] Add prerequisites section to getting-started docs
9494
- [x] Create first-run troubleshooting page
95-
- [ ] Implement `create-objectstack` CLI wizard with 3 templates
95+
- [x] Implement `create-objectstack` CLI wizard with 3 templates
9696
- [ ] Record 5-minute getting-started video
9797
- [x] Fix `examples/README.md`: remove `minimal-auth` ghost reference, update metadata (version, count, date)
9898
- [x] Fix `plugin-bi` example: add README.md, add package.json scripts, document purpose
@@ -158,7 +158,7 @@ This roadmap prioritizes improvements based on the **"Time to First Wow"** metri
158158
| Visual field type reference | Interactive page showing all 48 field types with live previews | 🔴 High | ✅ Done |
159159
| Field configuration reference | Per-type property tables (text: maxLength, pattern; number: min, max, precision) | 🔴 High | ✅ Done |
160160
| Field type decision tree | "Which field type should I use?" interactive guide | 🟡 Medium | ✅ Done |
161-
| Field validation rules per type | Default validation behavior for each field type | 🟡 Medium | ❌ Not started |
161+
| Field validation rules per type | Default validation behavior for each field type | 🟡 Medium | ✅ Done |
162162

163163
### 3.2 Error & Status Code Reference
164164

@@ -264,8 +264,8 @@ This roadmap prioritizes improvements based on the **"Time to First Wow"** metri
264264
- [ ] Enhance `objectstack doctor` (circular deps, missing tests, deprecated usage)
265265
- [ ] Implement `objectstack lint` for naming conventions
266266
- [ ] Implement `objectstack explain` for schema documentation
267-
- [ ] Create VSCode extension with autocomplete and validation
268-
- [ ] Add JSON Schema for `objectstack.config.ts` IDE support
267+
- [x] Create VSCode extension with autocomplete and validation
268+
- [x] Add JSON Schema for `objectstack.config.ts` IDE support
269269
- [ ] Implement `objectstack generate client` for typed SDK generation
270270
- [ ] Implement `objectstack generate migration` for schema diffs
271271
- [ ] Implement `objectstack codemod v2-to-v3` for automated migration
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { useState, useMemo, useCallback } from 'react';
4+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
5+
import { Button } from '@/components/ui/button';
6+
import { Badge } from '@/components/ui/badge';
7+
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
8+
import { Code2, Copy, Check, Database, Layout, Workflow, Bot, AppWindow } from 'lucide-react';
9+
10+
// ─── Types ──────────────────────────────────────────────────────────
11+
12+
type ExportType = 'object' | 'view' | 'flow' | 'agent' | 'app';
13+
14+
export interface CodeExporterProps {
15+
type: ExportType;
16+
definition: Record<string, unknown>;
17+
name?: string;
18+
}
19+
20+
// ─── Constants ──────────────────────────────────────────────────────
21+
22+
const TYPE_LABELS: Record<ExportType, { label: string; icon: React.ElementType }> = {
23+
object: { label: 'Object', icon: Database },
24+
view: { label: 'View', icon: Layout },
25+
flow: { label: 'Flow', icon: Workflow },
26+
agent: { label: 'Agent', icon: Bot },
27+
app: { label: 'App', icon: AppWindow },
28+
};
29+
30+
// ─── Code Generators ────────────────────────────────────────────────
31+
32+
function indent(str: string, level: number): string {
33+
const spaces = ' '.repeat(level);
34+
return str
35+
.split('\n')
36+
.map((line) => (line.trim() ? spaces + line : line))
37+
.join('\n');
38+
}
39+
40+
function formatValue(value: unknown, depth: number = 0): string {
41+
if (value === null || value === undefined) return 'undefined';
42+
if (typeof value === 'string') return `'${value.replace(/'/g, "\\'")}'`;
43+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
44+
45+
if (Array.isArray(value)) {
46+
if (value.length === 0) return '[]';
47+
const items = value.map((v) => formatValue(v, depth + 1));
48+
if (items.join(', ').length < 60) return `[${items.join(', ')}]`;
49+
return `[\n${items.map((item) => indent(item, depth + 1)).join(',\n')}\n${indent(']', depth)}`;
50+
}
51+
52+
if (typeof value === 'object') {
53+
const entries = Object.entries(value as Record<string, unknown>);
54+
if (entries.length === 0) return '{}';
55+
const lines = entries.map(
56+
([key, val]) => `${indent(`${key}: ${formatValue(val, depth + 1)}`, depth + 1)}`
57+
);
58+
if (lines.join(', ').length < 60 && !lines.some((l) => l.includes('\n'))) {
59+
const compact = entries.map(([k, v]) => `${k}: ${formatValue(v, depth + 1)}`).join(', ');
60+
return `{ ${compact} }`;
61+
}
62+
return `{\n${lines.join(',\n')}\n${indent('}', depth)}`;
63+
}
64+
65+
return String(value);
66+
}
67+
68+
function generateObjectCode(def: Record<string, unknown>, name?: string): string {
69+
const objName = name || (def.name as string) || 'my_object';
70+
const lines = [
71+
"import { defineStack } from '@objectstack/spec';",
72+
'',
73+
'export default defineStack({',
74+
' objects: {',
75+
` ${objName}: ${formatValue(def, 2)},`,
76+
' },',
77+
'});',
78+
];
79+
return lines.join('\n');
80+
}
81+
82+
function generateViewCode(def: Record<string, unknown>, name?: string): string {
83+
const viewName = name || (def.name as string) || 'my_view';
84+
const lines = [
85+
"import { defineStack } from '@objectstack/spec';",
86+
'',
87+
'export default defineStack({',
88+
' views: {',
89+
` ${viewName}: ${formatValue(def, 2)},`,
90+
' },',
91+
'});',
92+
];
93+
return lines.join('\n');
94+
}
95+
96+
function generateFlowCode(def: Record<string, unknown>, name?: string): string {
97+
const flowName = name || (def.name as string) || 'my_flow';
98+
const lines = [
99+
"import { defineStack } from '@objectstack/spec';",
100+
'',
101+
'export default defineStack({',
102+
' flows: {',
103+
` ${flowName}: ${formatValue(def, 2)},`,
104+
' },',
105+
'});',
106+
];
107+
return lines.join('\n');
108+
}
109+
110+
function generateAgentCode(def: Record<string, unknown>, name?: string): string {
111+
const agentName = name || (def.name as string) || 'my_agent';
112+
const lines = [
113+
"import { defineStack } from '@objectstack/spec';",
114+
'',
115+
'export default defineStack({',
116+
' agents: {',
117+
` ${agentName}: ${formatValue(def, 2)},`,
118+
' },',
119+
'});',
120+
];
121+
return lines.join('\n');
122+
}
123+
124+
function generateAppCode(def: Record<string, unknown>, name?: string): string {
125+
const appName = name || (def.name as string) || 'my_app';
126+
const lines = [
127+
"import { defineStack } from '@objectstack/spec';",
128+
'',
129+
'export default defineStack({',
130+
' apps: {',
131+
` ${appName}: ${formatValue(def, 2)},`,
132+
' },',
133+
'});',
134+
];
135+
return lines.join('\n');
136+
}
137+
138+
const CODE_GENERATORS: Record<ExportType, (def: Record<string, unknown>, name?: string) => string> = {
139+
object: generateObjectCode,
140+
view: generateViewCode,
141+
flow: generateFlowCode,
142+
agent: generateAgentCode,
143+
app: generateAppCode,
144+
};
145+
146+
// ─── Component ──────────────────────────────────────────────────────
147+
148+
export function CodeExporter({ type, definition, name }: CodeExporterProps) {
149+
const [format, setFormat] = useState<'typescript' | 'json'>('typescript');
150+
const [copied, setCopied] = useState(false);
151+
152+
const tsCode = useMemo(() => {
153+
const generator = CODE_GENERATORS[type];
154+
return generator ? generator(definition, name) : '// Unknown export type';
155+
}, [type, definition, name]);
156+
157+
const jsonCode = useMemo(() => JSON.stringify(definition, null, 2), [definition]);
158+
159+
const displayCode = format === 'typescript' ? tsCode : jsonCode;
160+
161+
const handleCopy = useCallback(() => {
162+
navigator.clipboard.writeText(displayCode);
163+
setCopied(true);
164+
setTimeout(() => setCopied(false), 1500);
165+
}, [displayCode]);
166+
167+
const TypeIcon = TYPE_LABELS[type]?.icon ?? Code2;
168+
169+
return (
170+
<Card>
171+
<CardHeader className="pb-3">
172+
<div className="flex items-center justify-between">
173+
<div className="space-y-1">
174+
<CardTitle className="text-base flex items-center gap-2">
175+
<Code2 className="h-4 w-4" />
176+
Export as Code
177+
</CardTitle>
178+
<CardDescription className="flex items-center gap-1.5">
179+
<Badge variant="outline" className="text-[10px] gap-1">
180+
<TypeIcon className="h-3 w-3" />
181+
{TYPE_LABELS[type]?.label ?? type}
182+
</Badge>
183+
{name && (
184+
<code className="text-xs font-mono bg-muted px-1 py-0.5 rounded">{name}</code>
185+
)}
186+
</CardDescription>
187+
</div>
188+
<div className="flex items-center gap-2">
189+
<Tabs value={format} onValueChange={(v) => setFormat(v as 'typescript' | 'json')}>
190+
<TabsList className="h-7">
191+
<TabsTrigger value="typescript" className="text-xs px-2 py-0.5">TypeScript</TabsTrigger>
192+
<TabsTrigger value="json" className="text-xs px-2 py-0.5">JSON</TabsTrigger>
193+
</TabsList>
194+
</Tabs>
195+
<Button variant="outline" size="sm" onClick={handleCopy} className="h-7 gap-1 text-xs">
196+
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
197+
{copied ? 'Copied!' : 'Copy to Clipboard'}
198+
</Button>
199+
</div>
200+
</div>
201+
</CardHeader>
202+
<CardContent className="p-0">
203+
<pre className="overflow-auto p-4 text-xs font-mono bg-muted/30 rounded-b-lg whitespace-pre max-h-[60vh]">
204+
<code>{displayCode}</code>
205+
</pre>
206+
</CardContent>
207+
</Card>
208+
);
209+
}
210+
211+
export default CodeExporter;

0 commit comments

Comments
 (0)