@nuraly/lumenjs 0.1.4 โ†’ 0.3.0

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.
Files changed (96) hide show
  1. package/README.md +48 -7
  2. package/dist/auth/native-auth.d.ts +9 -0
  3. package/dist/auth/native-auth.js +49 -2
  4. package/dist/auth/routes/login.js +24 -1
  5. package/dist/auth/routes/totp.d.ts +22 -0
  6. package/dist/auth/routes/totp.js +232 -0
  7. package/dist/auth/routes.js +14 -0
  8. package/dist/auth/token.js +2 -2
  9. package/dist/build/build-markdown.d.ts +15 -0
  10. package/dist/build/build-markdown.js +90 -0
  11. package/dist/build/build-server.d.ts +2 -1
  12. package/dist/build/build-server.js +12 -4
  13. package/dist/build/build.js +46 -5
  14. package/dist/build/scan.d.ts +1 -0
  15. package/dist/build/scan.js +2 -1
  16. package/dist/build/serve-static.js +2 -1
  17. package/dist/build/serve.js +131 -11
  18. package/dist/dev-server/config.js +18 -1
  19. package/dist/dev-server/index-html.d.ts +1 -0
  20. package/dist/dev-server/index-html.js +4 -1
  21. package/dist/dev-server/plugins/vite-plugin-llms.js +1 -0
  22. package/dist/dev-server/plugins/vite-plugin-loaders.d.ts +4 -3
  23. package/dist/dev-server/plugins/vite-plugin-loaders.js +4 -3
  24. package/dist/dev-server/plugins/vite-plugin-routes.js +3 -2
  25. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +34 -6
  26. package/dist/dev-server/server.js +146 -88
  27. package/dist/dev-server/ssr-render.js +10 -2
  28. package/dist/editor/ai/backend.js +11 -2
  29. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  30. package/dist/editor/ai/deepseek-client.js +113 -0
  31. package/dist/editor/ai/opencode-client.d.ts +1 -1
  32. package/dist/editor/ai/opencode-client.js +21 -47
  33. package/dist/editor/ai/types.d.ts +1 -1
  34. package/dist/editor/ai/types.js +2 -2
  35. package/dist/editor/ai-chat-panel.js +27 -1
  36. package/dist/editor/editor-bridge.js +2 -1
  37. package/dist/editor/overlay-hmr.js +2 -1
  38. package/dist/llms/generate.d.ts +15 -1
  39. package/dist/llms/generate.js +54 -44
  40. package/dist/runtime/app-shell.d.ts +1 -1
  41. package/dist/runtime/app-shell.js +1 -0
  42. package/dist/runtime/communication.d.ts +65 -36
  43. package/dist/runtime/communication.js +117 -57
  44. package/dist/runtime/island.d.ts +16 -0
  45. package/dist/runtime/island.js +80 -0
  46. package/dist/runtime/router-hydration.js +9 -2
  47. package/dist/runtime/router.d.ts +3 -1
  48. package/dist/runtime/router.js +51 -3
  49. package/dist/runtime/webrtc.d.ts +44 -0
  50. package/dist/runtime/webrtc.js +263 -13
  51. package/dist/shared/dom-shims.js +4 -2
  52. package/dist/shared/html-to-markdown.d.ts +6 -0
  53. package/dist/shared/html-to-markdown.js +73 -0
  54. package/dist/shared/types.d.ts +1 -0
  55. package/dist/storage/adapters/s3.js +6 -3
  56. package/package.json +33 -7
  57. package/templates/blog/pages/index.ts +3 -3
  58. package/templates/blog/pages/posts/[slug].ts +17 -6
  59. package/templates/blog/pages/tag/[tag].ts +6 -6
  60. package/templates/dashboard/pages/index.ts +7 -7
  61. package/templates/default/pages/index.ts +3 -3
  62. package/templates/social/api/posts/[id].ts +0 -14
  63. package/templates/social/api/posts.ts +0 -11
  64. package/templates/social/api/profile/[username].ts +0 -10
  65. package/templates/social/api/upload.ts +0 -19
  66. package/templates/social/data/migrations/001_init.sql +0 -78
  67. package/templates/social/data/migrations/002_add_image_url.sql +0 -1
  68. package/templates/social/data/migrations/003_auth.sql +0 -7
  69. package/templates/social/docs/architecture.md +0 -76
  70. package/templates/social/docs/components.md +0 -100
  71. package/templates/social/docs/data.md +0 -89
  72. package/templates/social/docs/pages.md +0 -96
  73. package/templates/social/docs/theming.md +0 -52
  74. package/templates/social/lib/media.ts +0 -130
  75. package/templates/social/lumenjs.auth.ts +0 -21
  76. package/templates/social/lumenjs.config.ts +0 -3
  77. package/templates/social/package.json +0 -5
  78. package/templates/social/pages/_layout.ts +0 -239
  79. package/templates/social/pages/apps/[id].ts +0 -173
  80. package/templates/social/pages/apps/index.ts +0 -116
  81. package/templates/social/pages/auth/login.ts +0 -92
  82. package/templates/social/pages/bookmarks.ts +0 -57
  83. package/templates/social/pages/explore.ts +0 -73
  84. package/templates/social/pages/index.ts +0 -351
  85. package/templates/social/pages/messages.ts +0 -298
  86. package/templates/social/pages/new.ts +0 -77
  87. package/templates/social/pages/notifications.ts +0 -73
  88. package/templates/social/pages/post/[id].ts +0 -124
  89. package/templates/social/pages/profile/[username].ts +0 -100
  90. package/templates/social/pages/settings/accessibility.ts +0 -153
  91. package/templates/social/pages/settings/account.ts +0 -260
  92. package/templates/social/pages/settings/help.ts +0 -141
  93. package/templates/social/pages/settings/language.ts +0 -103
  94. package/templates/social/pages/settings/privacy.ts +0 -183
  95. package/templates/social/pages/settings/security.ts +0 -133
  96. package/templates/social/pages/settings.ts +0 -185
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Convert simple HTML to markdown.
3
+ * Handles the subset of HTML that Lit SSR produces for LumenJS pages.
4
+ * Not a full HTML parser โ€” intentionally minimal.
5
+ */
6
+ export function htmlToMarkdown(html) {
7
+ let md = html;
8
+ // Extract content from declarative shadow DOM (<template shadowroot="open">...</template>)
9
+ md = md.replace(/<template\s+shadowroot(?:mode)?="open"[^>]*>([\s\S]*?)<\/template>/gi, '$1');
10
+ // Remove script/style tags and their content
11
+ md = md.replace(/<script[\s\S]*?<\/script>/gi, '');
12
+ md = md.replace(/<style[\s\S]*?<\/style>/gi, '');
13
+ md = md.replace(/<template[\s\S]*?<\/template>/gi, '');
14
+ // Remove Lit SSR markers (<!--lit-part-->, <!--/lit-part-->, etc.)
15
+ md = md.replace(/<!--[\s\S]*?-->/g, '');
16
+ // Headings
17
+ md = md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_, c) => `# ${strip(c)}\n\n`);
18
+ md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, (_, c) => `## ${strip(c)}\n\n`);
19
+ md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, (_, c) => `### ${strip(c)}\n\n`);
20
+ md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, (_, c) => `#### ${strip(c)}\n\n`);
21
+ // Links
22
+ md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => `[${strip(text)}](${href})`);
23
+ // Bold / italic
24
+ md = md.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, (_, __, c) => `**${strip(c)}**`);
25
+ md = md.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, (_, __, c) => `*${strip(c)}*`);
26
+ // Inline code
27
+ md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, c) => `\`${strip(c)}\``);
28
+ // Code blocks (pre > code or pre alone)
29
+ md = md.replace(/<pre[^>]*>\s*<code[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi, (_, c) => `\n\`\`\`\n${decodeEntities(strip(c))}\n\`\`\`\n\n`);
30
+ md = md.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_, c) => `\n\`\`\`\n${decodeEntities(strip(c))}\n\`\`\`\n\n`);
31
+ // List items
32
+ md = md.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, c) => `- ${strip(c).trim()}\n`);
33
+ // Table โ†’ simple text rows
34
+ md = md.replace(/<tr[^>]*>([\s\S]*?)<\/tr>/gi, (_, c) => {
35
+ const cells = [...c.matchAll(/<t[hd][^>]*>([\s\S]*?)<\/t[hd]>/gi)].map((m) => strip(m[1]).trim());
36
+ return cells.length > 0 ? `| ${cells.join(' | ')} |\n` : '';
37
+ });
38
+ // Paragraphs and divs โ†’ newlines
39
+ md = md.replace(/<\/p>/gi, '\n\n');
40
+ md = md.replace(/<br\s*\/?>/gi, '\n');
41
+ md = md.replace(/<\/div>/gi, '\n');
42
+ // Images
43
+ md = md.replace(/<img[^>]*alt="([^"]*)"[^>]*>/gi, (_, alt) => alt ? `[${alt}]` : '');
44
+ md = md.replace(/<img[^>]*>/gi, '');
45
+ // Strip all remaining HTML tags
46
+ md = md.replace(/<[^>]+>/g, '');
47
+ // Decode HTML entities
48
+ md = decodeEntities(md);
49
+ // Clean up whitespace
50
+ md = md.replace(/\n{3,}/g, '\n\n');
51
+ md = md.trim();
52
+ return md + '\n';
53
+ }
54
+ /** Strip HTML tags from a string. */
55
+ function strip(html) {
56
+ return html.replace(/<[^>]+>/g, '').trim();
57
+ }
58
+ /** Decode common HTML entities. */
59
+ function decodeEntities(text) {
60
+ return text
61
+ .replace(/&amp;/g, '&')
62
+ .replace(/&lt;/g, '<')
63
+ .replace(/&gt;/g, '>')
64
+ .replace(/&quot;/g, '"')
65
+ .replace(/&#39;/g, "'")
66
+ .replace(/&rarr;/g, 'โ†’')
67
+ .replace(/&larr;/g, 'โ†')
68
+ .replace(/&middot;/g, 'ยท')
69
+ .replace(/&copy;/g, 'ยฉ')
70
+ .replace(/\\u003c/g, '<')
71
+ .replace(/&#x27;/g, "'")
72
+ .replace(/&nbsp;/g, ' ');
73
+ }
@@ -9,6 +9,7 @@ export interface ManifestRoute {
9
9
  module: string;
10
10
  hasLoader: boolean;
11
11
  hasSubscribe: boolean;
12
+ hasSocket?: boolean;
12
13
  hasAuth?: boolean;
13
14
  hasMeta?: boolean;
14
15
  authRoles?: string[];
@@ -61,16 +61,19 @@ export class S3StorageAdapter {
61
61
  const mimeType = options?.mimeType ?? 'application/octet-stream';
62
62
  const acl = options?.acl ?? 'public-read';
63
63
  const { s3, PutObjectCommand } = await this.getClient();
64
- await s3.send(new PutObjectCommand({
64
+ const cmd = {
65
65
  Bucket: this.options.bucket,
66
66
  Key: key,
67
67
  Body: data,
68
68
  ContentType: mimeType,
69
- ACL: acl,
70
69
  ...(options?.fileName
71
70
  ? { ContentDisposition: `inline; filename="${options.fileName.replace(/[\r\n"\\]/g, '_')}"` }
72
71
  : {}),
73
- }));
72
+ };
73
+ // R2 and some S3-compatible APIs don't support ACL
74
+ if (!this.options.endpoint)
75
+ cmd.ACL = acl;
76
+ await s3.send(new PutObjectCommand(cmd));
74
77
  return {
75
78
  key,
76
79
  url: this.publicUrl(key),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuraly/lumenjs",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "Full-stack Lit web component framework with file-based routing, server loaders, SSR, and API routes",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -43,18 +43,20 @@
43
43
  "license": "MIT",
44
44
  "dependencies": {
45
45
  "@lit-labs/ssr": "^3.2.0",
46
- "better-sqlite3": "^12.8.0",
47
- "codejar": "^4.2.0",
48
46
  "glob": "^10.3.0",
49
47
  "lit": "^3.1.0",
50
- "pg": "^8.20.0",
51
- "socket.io": "^4.8.3",
52
- "socket.io-client": "^4.8.3",
53
48
  "vite": "^5.4.0"
54
49
  },
55
50
  "peerDependencies": {
56
51
  "@aws-sdk/client-s3": "^3.0.0",
57
- "@aws-sdk/s3-request-presigner": "^3.0.0"
52
+ "@aws-sdk/s3-request-presigner": "^3.0.0",
53
+ "better-sqlite3": "^12.8.0",
54
+ "codejar": "^4.2.0",
55
+ "otplib": "^12.0.0",
56
+ "pg": "^8.0.0",
57
+ "qrcode": "^1.5.0",
58
+ "socket.io": "^4.0.0",
59
+ "socket.io-client": "^4.0.0"
58
60
  },
59
61
  "peerDependenciesMeta": {
60
62
  "@aws-sdk/client-s3": {
@@ -62,12 +64,36 @@
62
64
  },
63
65
  "@aws-sdk/s3-request-presigner": {
64
66
  "optional": true
67
+ },
68
+ "better-sqlite3": {
69
+ "optional": true
70
+ },
71
+ "codejar": {
72
+ "optional": true
73
+ },
74
+ "pg": {
75
+ "optional": true
76
+ },
77
+ "socket.io": {
78
+ "optional": true
79
+ },
80
+ "socket.io-client": {
81
+ "optional": true
82
+ },
83
+ "otplib": {
84
+ "optional": true
85
+ },
86
+ "qrcode": {
87
+ "optional": true
65
88
  }
66
89
  },
67
90
  "devDependencies": {
68
91
  "@types/better-sqlite3": "^7.6.13",
69
92
  "@types/node": "^20.14.2",
93
+ "@types/qrcode": "^1.5.6",
70
94
  "@vitest/coverage-v8": "^4.0.18",
95
+ "otplib": "^12.0.1",
96
+ "qrcode": "^1.5.4",
71
97
  "typescript": "^5.4.5",
72
98
  "vitest": "^4.0.18"
73
99
  },
@@ -33,8 +33,8 @@ export async function loader() {
33
33
  }
34
34
 
35
35
  export class PageIndex extends LitElement {
36
- static properties = { loaderData: { type: Object } };
37
- loaderData: any = {};
36
+ static properties = { posts: { type: Array } };
37
+ posts: any[] = [];
38
38
 
39
39
  static styles = css`
40
40
  :host { display: block; }
@@ -49,7 +49,7 @@ export class PageIndex extends LitElement {
49
49
  `;
50
50
 
51
51
  render() {
52
- const posts = this.loaderData.posts || [];
52
+ const posts = this.posts || [];
53
53
  return html`
54
54
  <h1>Blog</h1>
55
55
  <p class="subtitle">Thoughts and tutorials</p>
@@ -29,8 +29,19 @@ export async function loader({ params }: { params: { slug: string } }) {
29
29
  }
30
30
 
31
31
  export class PagePost extends LitElement {
32
- static properties = { loaderData: { type: Object }, slug: { type: String } };
33
- loaderData: any = {};
32
+ static properties = {
33
+ title: { type: String },
34
+ date: { type: String },
35
+ content: { type: String },
36
+ readingTime: { type: Number },
37
+ notFound: { type: Boolean },
38
+ slug: { type: String },
39
+ };
40
+ title = '';
41
+ date = '';
42
+ content = '';
43
+ readingTime = 0;
44
+ notFound = false;
34
45
  slug = '';
35
46
 
36
47
  static styles = css`
@@ -44,7 +55,7 @@ export class PagePost extends LitElement {
44
55
  `;
45
56
 
46
57
  render() {
47
- if (this.loaderData.notFound) {
58
+ if (this.notFound) {
48
59
  return html`
49
60
  <a class="back" href="/posts">โ† Back to posts</a>
50
61
  <p class="not-found">Post not found.</p>
@@ -52,9 +63,9 @@ export class PagePost extends LitElement {
52
63
  }
53
64
  return html`
54
65
  <a class="back" href="/">โ† Back to posts</a>
55
- <h1>${this.loaderData.title}</h1>
56
- <div class="date">${this.loaderData.date} ยท ${this.loaderData.readingTime} min read</div>
57
- <p class="content">${this.loaderData.content}</p>
66
+ <h1>${this.title}</h1>
67
+ <div class="date">${this.date} ยท ${this.readingTime} min read</div>
68
+ <p class="content">${this.content}</p>
58
69
  `;
59
70
  }
60
71
  }
@@ -11,8 +11,9 @@ export async function loader({ params }: { params: { tag: string } }) {
11
11
  }
12
12
 
13
13
  export class PageTag extends LitElement {
14
- static properties = { loaderData: { type: Object } };
15
- loaderData: any = {};
14
+ static properties = { tag: { type: String }, posts: { type: Array } };
15
+ tag = '';
16
+ posts: any[] = [];
16
17
 
17
18
  static styles = css`
18
19
  :host { display: block; }
@@ -28,12 +29,11 @@ export class PageTag extends LitElement {
28
29
  `;
29
30
 
30
31
  render() {
31
- const { tag, posts } = this.loaderData;
32
32
  return html`
33
33
  <a class="back" href="/">โ† All posts</a>
34
- <h1>Tagged: ${tag}</h1>
35
- <p class="subtitle">${posts?.length || 0} post${posts?.length !== 1 ? 's' : ''}</p>
36
- ${(posts || []).map((p: any) => html`
34
+ <h1>Tagged: ${this.tag}</h1>
35
+ <p class="subtitle">${this.posts?.length || 0} post${this.posts?.length !== 1 ? 's' : ''}</p>
36
+ ${(this.posts || []).map((p: any) => html`
37
37
  <div class="post">
38
38
  <a href="/posts/${p.slug}">${p.title}</a>
39
39
  <div class="meta">${p.date}</div>
@@ -31,11 +31,11 @@ export function subscribe({ push }: { push: (data: any) => void }) {
31
31
 
32
32
  export class PageIndex extends LitElement {
33
33
  static properties = {
34
- loaderData: { type: Object },
35
- liveData: { type: Object },
34
+ stats: { type: Array },
35
+ updatedAt: { type: String },
36
36
  };
37
- loaderData: any = {};
38
- liveData: any = null;
37
+ stats: any[] = [];
38
+ updatedAt = '';
39
39
 
40
40
  static styles = css`
41
41
  :host { display: block; }
@@ -50,8 +50,8 @@ export class PageIndex extends LitElement {
50
50
  `;
51
51
 
52
52
  render() {
53
- const stats = this.liveData?.stats || this.loaderData.stats || [];
54
- const isLive = !!this.liveData;
53
+ const stats = this.stats || [];
54
+ const isLive = !!this.updatedAt;
55
55
  return html`
56
56
  <h1>Overview</h1>
57
57
  <div class="grid">
@@ -64,7 +64,7 @@ export class PageIndex extends LitElement {
64
64
  </div>
65
65
  ${isLive ? html`
66
66
  <div class="status">
67
- <span class="dot"></span>Live โ€” updated ${this.liveData.updatedAt ? new Date(this.liveData.updatedAt).toLocaleTimeString() : ''}
67
+ <span class="dot"></span>Live โ€” updated ${this.updatedAt ? new Date(this.updatedAt).toLocaleTimeString() : ''}
68
68
  </div>
69
69
  ` : ''}
70
70
  `;
@@ -5,8 +5,8 @@ export async function loader() {
5
5
  }
6
6
 
7
7
  export class PageIndex extends LitElement {
8
- static properties = { loaderData: { type: Object } };
9
- loaderData: any = {};
8
+ static properties = { title: { type: String } };
9
+ title = '';
10
10
 
11
11
  static styles = css`
12
12
  :host { display: block; max-width: 640px; margin: 0 auto; padding: 2rem; font-family: system-ui; }
@@ -17,7 +17,7 @@ export class PageIndex extends LitElement {
17
17
 
18
18
  render() {
19
19
  return html`
20
- <h1>${this.loaderData.title}</h1>
20
+ <h1>${this.title}</h1>
21
21
  <p>Edit <code>pages/index.ts</code> to get started.</p>
22
22
  `;
23
23
  }
@@ -1,14 +0,0 @@
1
- export function GET({ params }: { params: { id: string } }) {
2
- return Response.json({
3
- id: params.id,
4
- username: 'sarah_dev',
5
- display_name: 'Sarah Chen',
6
- content: 'Sample post content',
7
- likes: 42,
8
- replies: [],
9
- });
10
- }
11
-
12
- export function DELETE({ params }: { params: { id: string } }) {
13
- return Response.json({ ok: true, deleted: params.id });
14
- }
@@ -1,11 +0,0 @@
1
- export function GET() {
2
- return Response.json([
3
- { id: 1, username: 'sarah_dev', display_name: 'Sarah Chen', content: 'Just shipped a new feature using LumenJS!', likes: 42, replies: 5, shares: 12 },
4
- { id: 2, username: 'alex_design', display_name: 'Alex Rivera', content: 'Working on a new design system.', likes: 38, replies: 3, shares: 8 },
5
- { id: 3, username: 'emma_data', display_name: 'Emma Williams', content: 'Trained a new model on customer feedback data.', likes: 67, replies: 8, shares: 21 },
6
- ]);
7
- }
8
-
9
- export function POST() {
10
- return Response.json({ ok: true, message: 'Post created (mock)' });
11
- }
@@ -1,10 +0,0 @@
1
- export function GET({ params }: { params: { username: string } }) {
2
- return Response.json({
3
- username: params.username,
4
- display_name: params.username,
5
- bio: 'User profile (mock)',
6
- followers: 0,
7
- following: 0,
8
- posts: [],
9
- });
10
- }
@@ -1,19 +0,0 @@
1
- import crypto from 'crypto';
2
- import { compress } from '../lib/media.js';
3
-
4
- export async function POST(req: any) {
5
- const file = req.files?.[0];
6
- if (!file) throw { status: 400, message: 'No file provided' };
7
- if (!req.storage) throw { status: 503, message: 'Storage not configured' };
8
-
9
- const { data, mimeType, ext } = await compress(file.data, file.contentType);
10
- const key = `uploads/${crypto.randomUUID()}${ext}`;
11
-
12
- const stored = await req.storage.put(data, {
13
- key,
14
- mimeType,
15
- fileName: file.fileName,
16
- });
17
-
18
- return { url: stored.url, key: stored.key };
19
- }
@@ -1,78 +0,0 @@
1
- CREATE TABLE IF NOT EXISTS users (
2
- id INTEGER PRIMARY KEY AUTOINCREMENT,
3
- username TEXT NOT NULL UNIQUE,
4
- display_name TEXT NOT NULL,
5
- avatar_url TEXT NOT NULL DEFAULT '',
6
- bio TEXT NOT NULL DEFAULT '',
7
- cover_url TEXT NOT NULL DEFAULT '',
8
- followers_count INTEGER NOT NULL DEFAULT 0,
9
- following_count INTEGER NOT NULL DEFAULT 0,
10
- created_at TEXT NOT NULL DEFAULT (date('now'))
11
- );
12
-
13
- CREATE TABLE IF NOT EXISTS posts (
14
- id INTEGER PRIMARY KEY AUTOINCREMENT,
15
- user_id INTEGER NOT NULL REFERENCES users(id),
16
- content TEXT NOT NULL,
17
- parent_id INTEGER REFERENCES posts(id),
18
- likes_count INTEGER NOT NULL DEFAULT 0,
19
- replies_count INTEGER NOT NULL DEFAULT 0,
20
- shares_count INTEGER NOT NULL DEFAULT 0,
21
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
22
- );
23
-
24
- CREATE TABLE IF NOT EXISTS notifications (
25
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26
- user_id INTEGER NOT NULL REFERENCES users(id),
27
- type TEXT NOT NULL,
28
- actor_id INTEGER NOT NULL REFERENCES users(id),
29
- post_id INTEGER REFERENCES posts(id),
30
- read INTEGER NOT NULL DEFAULT 0,
31
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
32
- );
33
-
34
- CREATE TABLE IF NOT EXISTS trending (
35
- id INTEGER PRIMARY KEY AUTOINCREMENT,
36
- hashtag TEXT NOT NULL UNIQUE,
37
- post_count INTEGER NOT NULL DEFAULT 0
38
- );
39
-
40
- -- Seed users
41
- INSERT INTO users (username, display_name, avatar_url, bio, followers_count, following_count, created_at) VALUES
42
- ('sarah_dev', 'Sarah Chen', '๐Ÿง‘โ€๐Ÿ’ป', 'Full-stack developer. Open source enthusiast. Building the future one commit at a time.', 1240, 385, '2024-06-15'),
43
- ('alex_design', 'Alex Rivera', '๐ŸŽจ', 'UI/UX designer at heart. Pixels matter. Currently exploring design systems.', 890, 210, '2024-08-20'),
44
- ('mike_ops', 'Mike Johnson', '๐Ÿ› ๏ธ', 'DevOps engineer. Infrastructure as code. Coffee as fuel.', 560, 420, '2024-09-10'),
45
- ('emma_data', 'Emma Williams', '๐Ÿ“Š', 'Data scientist. ML enthusiast. Turning data into insights.', 2100, 150, '2024-03-05');
46
-
47
- -- Seed posts
48
- INSERT INTO posts (user_id, content, likes_count, replies_count, shares_count, created_at) VALUES
49
- (1, 'Just shipped a new feature using LumenJS! The file-based routing makes everything so clean. No more route config files! ๐Ÿš€', 42, 5, 12, '2025-03-21 14:30:00'),
50
- (2, 'Working on a new design system. The key insight: consistency beats creativity when building at scale. Every component should feel like it belongs.', 38, 3, 8, '2025-03-21 12:15:00'),
51
- (4, 'Trained a new model on customer feedback data. Accuracy went from 78% to 94% just by cleaning the input data. Garbage in, garbage out is real. ๐Ÿ“ˆ', 67, 8, 21, '2025-03-21 10:00:00'),
52
- (3, 'Migrated our entire CI/CD pipeline to GitHub Actions. 40% faster builds and way easier to maintain. Should have done this months ago.', 29, 4, 6, '2025-03-20 18:45:00'),
53
- (1, 'Hot take: TypeScript''s type system is a programming language in itself. And I love it. ๐Ÿ’™', 85, 12, 15, '2025-03-20 16:20:00'),
54
- (2, 'New blog post: "Why your loading states are making users anxious" โ€” skeleton screens vs spinners vs progress bars. Link in bio!', 51, 7, 19, '2025-03-20 11:30:00'),
55
- (4, 'PSA: If you''re using pandas, try polars. It''s not just faster โ€” the API is actually more intuitive once you get used to it.', 93, 15, 34, '2025-03-19 20:00:00'),
56
- (3, 'Docker tip: multi-stage builds reduced our image size from 1.2GB to 180MB. Always worth the extra 5 minutes of setup.', 44, 6, 11, '2025-03-19 15:30:00'),
57
- (1, 'Weekend project: built a CLI tool that generates commit messages from diffs using Claude. It''s surprisingly good at understanding context.', 112, 18, 28, '2025-03-18 09:15:00'),
58
- (2, 'The best design feedback I ever got: "Can my grandma use this?" Simple but powerful heuristic. ๐Ÿ‘ต', 76, 9, 22, '2025-03-17 14:00:00');
59
-
60
- -- Seed replies (parent_id references)
61
- INSERT INTO posts (user_id, content, parent_id, likes_count, created_at) VALUES
62
- (2, 'Love the file-based routing approach! How does it handle dynamic params?', 1, 8, '2025-03-21 14:45:00'),
63
- (3, 'Totally agree on TypeScript. The conditional types are mind-bending though ๐Ÿ˜…', 5, 12, '2025-03-20 16:35:00'),
64
- (4, 'Multi-stage builds are a game changer. We use them everywhere now.', 8, 5, '2025-03-19 15:45:00');
65
-
66
- -- Seed notifications (for user 1 - sarah_dev)
67
- INSERT INTO notifications (user_id, type, actor_id, post_id, created_at) VALUES
68
- (1, 'like', 2, 1, '2025-03-21 14:35:00'),
69
- (1, 'reply', 2, 1, '2025-03-21 14:45:00'),
70
- (1, 'follow', 3, NULL, '2025-03-21 13:00:00'),
71
- (1, 'like', 4, 5, '2025-03-20 16:25:00'),
72
- (1, 'reply', 3, 5, '2025-03-20 16:35:00');
73
-
74
- -- Seed trending
75
- INSERT INTO trending (hashtag, post_count) VALUES
76
- ('#WebComponents', 1250),
77
- ('#TypeScript', 3400),
78
- ('#DevOps', 890);
@@ -1 +0,0 @@
1
- ALTER TABLE posts ADD COLUMN image_url TEXT NOT NULL DEFAULT '';
@@ -1,7 +0,0 @@
1
- -- Add OAuth identity columns to users table
2
- ALTER TABLE users ADD COLUMN oauth_provider TEXT;
3
- ALTER TABLE users ADD COLUMN oauth_sub TEXT;
4
- ALTER TABLE users ADD COLUMN email TEXT;
5
-
6
- -- Prevent duplicate OAuth accounts
7
- CREATE UNIQUE INDEX IF NOT EXISTS idx_users_oauth_sub ON users(oauth_provider, oauth_sub);
@@ -1,76 +0,0 @@
1
- # Social Template โ€” Architecture
2
-
3
- A social networking super app template for LumenJS, inspired by Twitter, LinkedIn, and dev.to.
4
-
5
- **Demo**: https://social.lumenjs.dev
6
-
7
- ## Tech Stack
8
-
9
- - **LumenJS** โ€” file-based routing, SSR with Lit SSR, server loaders, API routes
10
- - **Lit** โ€” web components with Shadow DOM, reactive properties, CSS-in-JS
11
- - **SQLite** โ€” mock data via migrations (seeded on first run)
12
- - **No frameworks** โ€” pure Lit + native HTML elements, no Tailwind, no NuralyUI
13
-
14
- ## Directory Structure
15
-
16
- ```
17
- templates/social/
18
- โ”œโ”€โ”€ lumenjs.config.ts # App config (title only)
19
- โ”œโ”€โ”€ package.json # Template metadata
20
- โ”œโ”€โ”€ data/
21
- โ”‚ โ””โ”€โ”€ migrations/
22
- โ”‚ โ””โ”€โ”€ 001_init.sql # DB schema + seed data
23
- โ”œโ”€โ”€ api/
24
- โ”‚ โ”œโ”€โ”€ posts.ts # GET /api/posts, POST /api/posts
25
- โ”‚ โ”œโ”€โ”€ posts/[id].ts # GET /api/posts/:id, DELETE
26
- โ”‚ โ””โ”€โ”€ profile/[username].ts # GET /api/profile/:username
27
- โ”œโ”€โ”€ pages/
28
- โ”‚ โ”œโ”€โ”€ _layout.ts # App shell (sidebar, right panel, mobile nav, theming)
29
- โ”‚ โ”œโ”€โ”€ index.ts # Feed with stories bar + widget cards
30
- โ”‚ โ”œโ”€โ”€ explore.ts # Tags, suggested users, top posts
31
- โ”‚ โ”œโ”€โ”€ notifications.ts # Notification list with type icons
32
- โ”‚ โ”œโ”€โ”€ messages.ts # Conversations + chat detail
33
- โ”‚ โ”œโ”€โ”€ bookmarks.ts # Saved posts list
34
- โ”‚ โ”œโ”€โ”€ settings.ts # Settings with dark mode toggle
35
- โ”‚ โ”œโ”€โ”€ new.ts # New post composer
36
- โ”‚ โ”œโ”€โ”€ post/
37
- โ”‚ โ”‚ โ””โ”€โ”€ [id].ts # Post detail with comments
38
- โ”‚ โ”œโ”€โ”€ profile/
39
- โ”‚ โ”‚ โ””โ”€โ”€ [username].ts # User profile with posts
40
- โ”‚ โ””โ”€โ”€ apps/
41
- โ”‚ โ”œโ”€โ”€ index.ts # App marketplace
42
- โ”‚ โ””โ”€โ”€ [id].ts # Individual app view
43
- โ””โ”€โ”€ docs/ # This documentation
44
- ```
45
-
46
- ## Pages & Routes
47
-
48
- | Route | File | Description |
49
- |-------|------|-------------|
50
- | `/` | `pages/index.ts` | Feed with stories, posts, widget cards |
51
- | `/explore` | `pages/explore.ts` | Tags, users, popular posts |
52
- | `/notifications` | `pages/notifications.ts` | Activity notifications |
53
- | `/messages` | `pages/messages.ts` | Chat conversations |
54
- | `/bookmarks` | `pages/bookmarks.ts` | Saved posts |
55
- | `/settings` | `pages/settings.ts` | Account & theme settings |
56
- | `/new` | `pages/new.ts` | Create new post |
57
- | `/post/:id` | `pages/post/[id].ts` | Single post with comments |
58
- | `/profile/:username` | `pages/profile/[username].ts` | User profile |
59
- | `/apps` | `pages/apps/index.ts` | App marketplace |
60
- | `/apps/:id` | `pages/apps/[id].ts` | Individual app |
61
-
62
- ## Layout Architecture
63
-
64
- `_layout.ts` is the root layout wrapping all pages via `<slot>`.
65
-
66
- **Desktop** (>860px): icon sidebar (68px) + main content (600px max) + right sidebar (350px)
67
- **Tablet** (640-860px): sidebar + main content (right sidebar hidden)
68
- **Mobile** (<640px): mobile top bar + full-width content + bottom nav bar + more menu sheet
69
-
70
- ## Theming
71
-
72
- Dark mode via CSS custom properties set on `document.documentElement`. Toggle in settings persists to `localStorage('theme')`. See `docs/theming.md`.
73
-
74
- ## Data
75
-
76
- All data is mock โ€” hardcoded in loaders and API handlers. SQLite migration seeds initial data. See `docs/data.md`.
@@ -1,100 +0,0 @@
1
- # Shared Patterns & Components
2
-
3
- ## SVG Icon Library
4
-
5
- All icons are inline SVG in Lit `html` tagged templates. Style: feather icons (stroke, no fill, 1.5 stroke-width, 22x22 default).
6
-
7
- ### Layout icons (`_layout.ts`)
8
- - `home` โ€” house with chimney
9
- - `explore` โ€” magnifying glass (search)
10
- - `bell` โ€” notification bell
11
- - `message` โ€” envelope
12
- - `user` โ€” person silhouette
13
- - `search` โ€” small magnifying glass (16x16)
14
- - `post` โ€” pen/edit
15
- - `bookmark` โ€” bookmark flag
16
- - `plus` โ€” plus sign (24x24, 2 stroke-width)
17
- - `settings` โ€” gear/cog
18
-
19
- ### Feed icons (`index.ts`)
20
- - `comment` โ€” chat bubble (17x17)
21
- - `repost` โ€” circular arrows (17x17)
22
- - `heart` โ€” heart outline (17x17)
23
- - `share` โ€” upload/share arrow (17x17)
24
-
25
- ### Post detail icons (`post/[id].ts`)
26
- - `heart`, `comment`, `bookmark` โ€” 18x18 variants
27
-
28
- ### Profile icon (`profile/[username].ts`)
29
- - `location` โ€” map pin (14x14)
30
- - `briefcase` โ€” work bag (14x14)
31
- - `calendar` โ€” calendar grid (14x14)
32
- - Follow button: user-plus (18x18, 2 stroke-width)
33
-
34
- ### Messages icons (`messages.ts`)
35
- - `send`, `image`, `smile`, `check`, `checkDouble`, `phone`, `video`, `info`, `attach`, `mic`, `search`
36
-
37
- ### Notifications icons (`notifications.ts`)
38
- - `heart` โ€” filled red (#e53935)
39
- - `comment` โ€” filled blue (#3b49df)
40
- - `userPlus` โ€” stroke green (#22c55e)
41
-
42
- ### New post icons (`new.ts`)
43
- - `image`, `video`, `smile`, `mapPin`, `globe`, `close`
44
-
45
- ### Settings icons (`settings.ts`)
46
- - `user`, `lock`, `bell`, `eye`, `palette`, `globe`, `shield`, `help`, `logout`, `chevron`
47
-
48
- ## Post Card Pattern
49
-
50
- Used in feed, profile, and bookmarks:
51
- ```
52
- <a class="post" href="/post/${id}">
53
- <div class="post-avatar" style="background:${color}">
54
- ${avatar ? <img> : initials}
55
- </div>
56
- <div class="post-body">
57
- <div class="post-header">name + @handle + ยท + time</div>
58
- <div class="post-content">text (3-line clamp)</div>
59
- <div class="post-media">image or video (16:9)</div>
60
- <div class="post-actions">comment + repost + heart + share</div>
61
- </div>
62
- </a>
63
- ```
64
-
65
- ## Widget Card Pattern
66
-
67
- Used for poll, event, job in feed:
68
- ```
69
- <div class="widget-card">
70
- <div class="widget-header">
71
- avatar + name + time + badge (Poll/Event/Job)
72
- </div>
73
- <div class="...">custom widget body</div>
74
- </div>
75
- ```
76
-
77
- ## Story Thumbnail
78
-
79
- ```
80
- <div class="story">
81
- <div class="story-ring">
82
- <div class="story-avatar-wrap">
83
- <div class="story-avatar"><img></div>
84
- (optional: story-plus for "You")
85
- </div>
86
- </div>
87
- <span class="story-name">Name</span>
88
- </div>
89
- ```
90
- 56x80px rounded rectangles with border. Click opens full-screen viewer.
91
-
92
- ## Mobile Patterns
93
-
94
- **Bottom bar**: 5 items โ€” Home, Explore, + (purple FAB), Messages, More (three dots)
95
-
96
- **More menu**: overlay + bottom sheet with drag handle + menu items (Profile, Bookmarks, Apps, Notifications, Settings)
97
-
98
- **Mobile top bar**: avatar + "Home" text (link to /) + spacer + settings gear + bell + message icons
99
-
100
- **Message mobile**: click conversation โ†’ `_showChat` state toggles `.chat-open` class โ†’ hides list, shows full-screen chat with back arrow