@stone-js/starters 0.8.4 → 0.8.6

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 (55) hide show
  1. package/basic-react-declarative/app/Application.tsx +22 -7
  2. package/basic-react-declarative/assets/css/index.css +163 -18
  3. package/basic-react-declarative/package.json +8 -5
  4. package/basic-react-declarative/tests/Application.spec.ts +2 -2
  5. package/basic-react-imperative/app/Application.tsx +22 -7
  6. package/basic-react-imperative/assets/css/index.css +163 -18
  7. package/basic-react-imperative/package.json +8 -5
  8. package/basic-react-imperative/tests/Application.spec.ts +4 -2
  9. package/basic-service-declarative/app/Application.ts +18 -3
  10. package/basic-service-declarative/package.json +7 -4
  11. package/basic-service-declarative/tests/Application.spec.ts +3 -2
  12. package/basic-service-imperative/app/Application.ts +18 -3
  13. package/basic-service-imperative/package.json +7 -4
  14. package/basic-service-imperative/tests/Application.spec.ts +3 -2
  15. package/continuum-showcase/app/layouts/MainLayout.tsx +13 -7
  16. package/continuum-showcase/app/pages/BlogPostPage.tsx +5 -2
  17. package/continuum-showcase/app/pages/HomePage.tsx +19 -6
  18. package/continuum-showcase/assets/css/index.css +190 -18
  19. package/continuum-showcase/package.json +14 -8
  20. package/continuum-showcase/tests/HomePage.spec.tsx +4 -3
  21. package/full-react-declarative/app/middleware/AuthMiddleware.ts +1 -1
  22. package/full-react-declarative/app/pages/home/HomePage.tsx +22 -21
  23. package/full-react-declarative/assets/css/index.css +167 -0
  24. package/full-react-declarative/package.json +13 -10
  25. package/full-react-declarative/tests/HomePage.spec.ts +38 -0
  26. package/full-react-imperative/app/middleware/AuthMiddleware.ts +1 -1
  27. package/full-react-imperative/app/pages/home/HomePage.tsx +32 -16
  28. package/full-react-imperative/assets/css/index.css +167 -0
  29. package/full-react-imperative/package.json +13 -10
  30. package/full-react-imperative/tests/HomePage.spec.ts +45 -0
  31. package/full-service-declarative/app/handlers/WelcomeEventHandler.ts +47 -0
  32. package/full-service-declarative/app/middleware/AuthMiddleware.ts +1 -1
  33. package/full-service-declarative/package.json +13 -10
  34. package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +54 -0
  35. package/full-service-imperative/app/handlers/WelcomeEventHandler.ts +51 -0
  36. package/full-service-imperative/app/middleware/AuthMiddleware.ts +1 -1
  37. package/full-service-imperative/package.json +13 -10
  38. package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +31 -0
  39. package/package.json +6 -7
  40. package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +37 -5
  41. package/standard-react-declarative/assets/css/index.css +163 -18
  42. package/standard-react-declarative/package.json +11 -8
  43. package/standard-react-declarative/tests/WelcomePage.spec.ts +2 -0
  44. package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +37 -5
  45. package/standard-react-imperative/assets/css/index.css +163 -18
  46. package/standard-react-imperative/package.json +11 -8
  47. package/standard-react-imperative/tests/WelcomePage.spec.ts +2 -0
  48. package/standard-service-declarative/app/handlers/WelcomeEventHandler.ts +2 -2
  49. package/standard-service-declarative/app/services/WelcomeService.ts +28 -2
  50. package/standard-service-declarative/package.json +9 -6
  51. package/standard-service-declarative/tests/Application.spec.ts +13 -4
  52. package/standard-service-imperative/app/handlers/welcomeEventHandler.ts +2 -4
  53. package/standard-service-imperative/app/services/welcomeService.ts +28 -2
  54. package/standard-service-imperative/package.json +9 -6
  55. package/standard-service-imperative/tests/Application.spec.ts +12 -3
@@ -22,27 +22,30 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@libsql/client": "^0.14.0",
25
- "@stone-js/core": "^0.8.4",
26
- "@stone-js/env": "^0.8.4",
27
- "@stone-js/filesystem": "^0.8.4",
28
- "@stone-js/http-core": "^0.8.4",
29
- "@stone-js/node-cli-adapter": "^0.8.4",
30
- "@stone-js/node-http-adapter": "^0.8.4",
31
- "@stone-js/pipeline": "^0.8.4",
32
- "@stone-js/router": "^0.8.4",
25
+ "@stone-js/core": "^0.8.6",
26
+ "@stone-js/env": "^0.8.6",
27
+ "@stone-js/filesystem": "^0.8.6",
28
+ "@stone-js/http-core": "^0.8.6",
29
+ "@stone-js/node-cli-adapter": "^0.8.6",
30
+ "@stone-js/node-http-adapter": "^0.8.6",
31
+ "@stone-js/pipeline": "^0.8.6",
32
+ "@stone-js/router": "^0.8.6",
33
33
  "bcryptjs": "^3.0.2",
34
- "drizzle-orm": "^0.44.2",
34
+ "drizzle-orm": "^0.44.7",
35
35
  "jsonwebtoken": "^9.0.2"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@babel/plugin-proposal-decorators": "^7.25.9",
39
39
  "@babel/preset-env": "^7.26.9",
40
40
  "@babel/preset-typescript": "^7.27.0",
41
- "@stone-js/cli": "^0.8.4",
41
+ "@stone-js/cli": "^0.8.6",
42
42
  "@types/jsonwebtoken": "^9.0.8",
43
43
  "@types/node": "^24.0.3",
44
44
  "@vitest/coverage-v8": "^3.0.9",
45
45
  "drizzle-kit": "^0.31.1",
46
46
  "vitest": "^3.0.9"
47
+ },
48
+ "overrides": {
49
+ "glob": "^13.0.0"
47
50
  }
48
51
  }
@@ -0,0 +1,54 @@
1
+ import { WelcomeEventHandler } from '../app/handlers/WelcomeEventHandler'
2
+ import { IncomingHttpEvent } from '@stone-js/http-core'
3
+
4
+ // Neutralize the routing/http decorators to lighten the test environment,
5
+ // while asserting the real branded payload the handler returns.
6
+ vi.mock(import('@stone-js/router'), async (importOriginal) => {
7
+ const actual = await importOriginal()
8
+ return {
9
+ ...actual,
10
+ EventHandler: vi.fn(() => vi.fn()),
11
+ Get: vi.fn(() => vi.fn()),
12
+ }
13
+ })
14
+
15
+ vi.mock(import('@stone-js/http-core'), async (importOriginal) => {
16
+ const actual = await importOriginal()
17
+ return {
18
+ ...actual,
19
+ JsonHttpResponse: vi.fn(() => vi.fn()),
20
+ }
21
+ })
22
+
23
+ describe('WelcomeEventHandler', () => {
24
+ let handler: WelcomeEventHandler
25
+
26
+ beforeEach(() => {
27
+ handler = new WelcomeEventHandler()
28
+ })
29
+
30
+ const makeEvent = (name?: string): IncomingHttpEvent => ({
31
+ get: vi.fn((_key: string, fallback: string) => name ?? fallback),
32
+ }) as unknown as IncomingHttpEvent
33
+
34
+ it('should return a branded Stone.js payload greeting the given name', () => {
35
+ // Act
36
+ const payload = handler.welcome(makeEvent('Ada'))
37
+
38
+ // Assert
39
+ expect(payload.message).toBe('Hello Ada! Welcome to Stone.js.')
40
+ expect(payload.framework.name).toBe('Stone.js')
41
+ expect(payload.framework.tagline).toBe('The continuum framework')
42
+ expect(payload.framework.docs).toBe('https://stonejs.dev')
43
+ expect(payload.framework.github).toBe('https://github.com/stone-foundation/stone-js-framework')
44
+ })
45
+
46
+ it('should fall back to the default name when none is provided', () => {
47
+ // Act
48
+ const payload = handler.welcome(makeEvent())
49
+
50
+ // Assert
51
+ expect(payload.message).toBe('Hello Stone! Welcome to Stone.js.')
52
+ expect(payload.framework.name).toBe('Stone.js')
53
+ })
54
+ })
@@ -0,0 +1,51 @@
1
+ import { IncomingHttpEvent } from '@stone-js/http-core'
2
+ import { FunctionalEventHandler } from '@stone-js/core'
3
+ import { defineRoutes, GET } from '@stone-js/router'
4
+
5
+ /**
6
+ * Framework brand descriptor returned by the welcome endpoint.
7
+ */
8
+ export interface FrameworkBrand {
9
+ name: string
10
+ tagline: string
11
+ docs: string
12
+ github: string
13
+ }
14
+
15
+ /**
16
+ * Branded welcome payload.
17
+ */
18
+ export interface WelcomePayload {
19
+ message: string
20
+ framework: FrameworkBrand
21
+ }
22
+
23
+ /**
24
+ * Welcome event handler factory.
25
+ *
26
+ * Greets the caller at the application root with a branded Stone.js payload.
27
+ * The `name` query param keeps the domain behavior meaningful.
28
+ */
29
+ export function factoryWelcomeEventHandler (): FunctionalEventHandler<IncomingHttpEvent> {
30
+ return function (event: IncomingHttpEvent): WelcomePayload {
31
+ const name = event.get<string>('name', 'Stone')
32
+ return {
33
+ message: `Hello ${name}! Welcome to Stone.js.`,
34
+ framework: {
35
+ name: 'Stone.js',
36
+ tagline: 'The continuum framework',
37
+ docs: 'https://stonejs.dev',
38
+ github: 'https://github.com/stone-foundation/stone-js-framework'
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Welcome Event Handler
46
+ */
47
+ export const WelcomeEventHandler = defineRoutes(
48
+ [
49
+ [factoryWelcomeEventHandler, { isFactory: true, path: '/', method: GET, name: 'welcome.index' }]
50
+ ]
51
+ )
@@ -41,4 +41,4 @@ export function factoryAuthMiddleware ({ securityService }: AuthMiddlewareOption
41
41
  /**
42
42
  * Auth middleware
43
43
  */
44
- export const AuthMiddleware = defineMiddleware(factoryAuthMiddleware, { isFactory: true, global: true, params: [['/login', '/register']] })
44
+ export const AuthMiddleware = defineMiddleware(factoryAuthMiddleware, { isFactory: true, global: true, params: [['/', '/login', '/register']] })
@@ -22,27 +22,30 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@libsql/client": "^0.14.0",
25
- "@stone-js/core": "^0.8.4",
26
- "@stone-js/env": "^0.8.4",
27
- "@stone-js/filesystem": "^0.8.4",
28
- "@stone-js/http-core": "^0.8.4",
29
- "@stone-js/node-cli-adapter": "^0.8.4",
30
- "@stone-js/node-http-adapter": "^0.8.4",
31
- "@stone-js/pipeline": "^0.8.4",
32
- "@stone-js/router": "^0.8.4",
25
+ "@stone-js/core": "^0.8.6",
26
+ "@stone-js/env": "^0.8.6",
27
+ "@stone-js/filesystem": "^0.8.6",
28
+ "@stone-js/http-core": "^0.8.6",
29
+ "@stone-js/node-cli-adapter": "^0.8.6",
30
+ "@stone-js/node-http-adapter": "^0.8.6",
31
+ "@stone-js/pipeline": "^0.8.6",
32
+ "@stone-js/router": "^0.8.6",
33
33
  "bcryptjs": "^3.0.2",
34
- "drizzle-orm": "^0.44.2",
34
+ "drizzle-orm": "^0.44.7",
35
35
  "jsonwebtoken": "^9.0.2"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@babel/plugin-proposal-decorators": "^7.25.9",
39
39
  "@babel/preset-env": "^7.26.9",
40
40
  "@babel/preset-typescript": "^7.27.0",
41
- "@stone-js/cli": "^0.8.4",
41
+ "@stone-js/cli": "^0.8.6",
42
42
  "@types/jsonwebtoken": "^9.0.8",
43
43
  "@types/node": "^24.0.3",
44
44
  "@vitest/coverage-v8": "^3.0.9",
45
45
  "drizzle-kit": "^0.31.1",
46
46
  "vitest": "^3.0.9"
47
+ },
48
+ "overrides": {
49
+ "glob": "^13.0.0"
47
50
  }
48
51
  }
@@ -0,0 +1,31 @@
1
+ import { IncomingHttpEvent } from '@stone-js/http-core'
2
+ import { factoryWelcomeEventHandler, WelcomePayload } from '../app/handlers/WelcomeEventHandler'
3
+
4
+ describe('factoryWelcomeEventHandler', () => {
5
+ const makeEvent = (name?: string): IncomingHttpEvent => ({
6
+ get: vi.fn((_key: string, fallback: string) => name ?? fallback),
7
+ }) as unknown as IncomingHttpEvent
8
+
9
+ it('should return a branded Stone.js payload greeting the given name', () => {
10
+ // Act
11
+ const handler = factoryWelcomeEventHandler()
12
+ const payload = handler(makeEvent('Ada')) as unknown as WelcomePayload
13
+
14
+ // Assert
15
+ expect(payload.message).toBe('Hello Ada! Welcome to Stone.js.')
16
+ expect(payload.framework.name).toBe('Stone.js')
17
+ expect(payload.framework.tagline).toBe('The continuum framework')
18
+ expect(payload.framework.docs).toBe('https://stonejs.dev')
19
+ expect(payload.framework.github).toBe('https://github.com/stone-foundation/stone-js-framework')
20
+ })
21
+
22
+ it('should fall back to the default name when none is provided', () => {
23
+ // Act
24
+ const handler = factoryWelcomeEventHandler()
25
+ const payload = handler(makeEvent()) as unknown as WelcomePayload
26
+
27
+ // Assert
28
+ expect(payload.message).toBe('Hello Stone! Welcome to Stone.js.')
29
+ expect(payload.framework.name).toBe('Stone.js')
30
+ })
31
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stone-js/starters",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "This repository contains official **starter templates** for Stone.js applications.",
5
5
  "author": "Mr. Stone <evensstone@gmail.com>",
6
6
  "license": "MIT",
@@ -183,11 +183,6 @@
183
183
  }
184
184
  ]
185
185
  },
186
- "scripts": {
187
- "lint": "ts-standard **/app",
188
- "lint:fix": "ts-standard --fix **/app",
189
- "prepare": "husky"
190
- },
191
186
  "devDependencies": {
192
187
  "@commitlint/cli": "^19.8.1",
193
188
  "@commitlint/config-conventional": "^19.8.1",
@@ -221,5 +216,9 @@
221
216
  ],
222
217
  "publishConfig": {
223
218
  "access": "public"
219
+ },
220
+ "scripts": {
221
+ "lint": "ts-standard **/app",
222
+ "lint:fix": "ts-standard --fix **/app"
224
223
  }
225
- }
224
+ }
@@ -1,6 +1,7 @@
1
1
  import { JSX } from 'react'
2
+ import { Promiseable } from '@stone-js/core'
2
3
  import { WelcomeService } from '../../services/WelcomeService'
3
- import { Page, ReactIncomingEvent, PageRenderContext, IPage } from '@stone-js/use-react'
4
+ import { Page, ReactIncomingEvent, PageRenderContext, PageHeadContext, HeadContext, IPage } from '@stone-js/use-react'
4
5
 
5
6
  /**
6
7
  * WelcomePage Options
@@ -42,6 +43,22 @@ export class WelcomePage implements IPage<ReactIncomingEvent> {
42
43
  return this.welcomeService.welcome(event.get<string>('name', 'World'))
43
44
  }
44
45
 
46
+ /**
47
+ * Set the page head tags, like title, meta, link, script, style.
48
+ *
49
+ * @returns The head context.
50
+ */
51
+ head ({ event }: PageHeadContext): Promiseable<HeadContext> {
52
+ return {
53
+ title: `${String(event.get<string>('name', 'World'))} · Welcome to Stone.js`,
54
+ description: 'A universal Stone.js application. Write your domain once; Stone.js is the context that runs it anywhere.',
55
+ metas: [
56
+ { name: 'author', content: 'Stone.js' },
57
+ { name: 'keywords', content: 'stonejs,continuum,react,universal,framework' }
58
+ ]
59
+ }
60
+ }
61
+
45
62
  /**
46
63
  * Render the component.
47
64
  *
@@ -49,10 +66,25 @@ export class WelcomePage implements IPage<ReactIncomingEvent> {
49
66
  */
50
67
  render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
51
68
  return (
52
- <section className='container'>
53
- <img src='/logo.svg' alt='Stone.js' width={96} height={96} />
54
- <h1 className='h1 text-center mt-64'>{data?.message}</h1>
55
- </section>
69
+ <main className='stone-welcome'>
70
+ <div className='glow' aria-hidden='true' />
71
+ <section className='hero'>
72
+ <img className='mark' src='/logo.svg' alt='Stone.js' width={104} height={104} />
73
+ <p className='eyebrow'>Welcome to</p>
74
+ <h1 className='title'>Stone.js</h1>
75
+ <p className='lead'>{data?.message}</p>
76
+ <p className='tagline'>
77
+ Your app is running. Write your domain once, Stone.js is the context that runs it
78
+ anywhere: server, serverless, browser, CLI and the edge.
79
+ </p>
80
+ <nav className='links'>
81
+ <a href='https://stonejs.dev/docs' target='_blank' rel='noreferrer noopener'>Documentation</a>
82
+ <a href='https://github.com/stone-foundation/stone-js-framework' target='_blank' rel='noreferrer noopener'>GitHub</a>
83
+ <span className='edit'>Edit <b>app/pages/welcome/WelcomePage.tsx</b></span>
84
+ </nav>
85
+ </section>
86
+ <footer className='brand'><span className='dot'>●</span> Stone.js — the continuum framework</footer>
87
+ </main>
56
88
  )
57
89
  }
58
90
  }
@@ -1,26 +1,171 @@
1
- body {
2
- margin: 0;
3
- padding: 0;
4
- color: #333;
5
- font-size: 14px;
6
- line-height: 1.5;
7
- background-color: #fff;
8
- font-family: 'Roboto', sans-serif;
1
+ /**
2
+ * Stone.js starter — brand welcome screen.
3
+ * Identity: "Obsidienne & Braise" — obsidian ground, ember (braise) accent, the Portal mark.
4
+ * Theme-aware: dark by default, light under prefers-color-scheme: light.
5
+ */
6
+ :root {
7
+ --ink: #14100E;
8
+ --ink-2: #1E1815;
9
+ --cream: #F4EFE6;
10
+ --cream-dim: #B8AE9E;
11
+ --ember-1: #FFC46B;
12
+ --ember-2: #FF5A1F;
13
+ --line: rgba(244, 239, 230, 0.10);
14
+ --card: rgba(244, 239, 230, 0.04);
15
+ --bg: radial-gradient(1200px 600px at 50% -10%, #241C17 0%, var(--ink) 55%);
9
16
  }
10
- .h1 {
17
+
18
+ @media (prefers-color-scheme: light) {
19
+ :root {
20
+ --ink: #F4EFE6;
21
+ --ink-2: #ffffff;
22
+ --cream: #1B1613;
23
+ --cream-dim: #6B6154;
24
+ --line: rgba(27, 22, 19, 0.10);
25
+ --card: rgba(27, 22, 19, 0.03);
26
+ --bg: radial-gradient(1200px 600px at 50% -10%, #FFFFFF 0%, #F1E9DC 60%);
27
+ }
28
+ }
29
+
30
+ * { box-sizing: border-box; }
31
+
32
+ body {
11
33
  margin: 0;
12
- font-weight: 500;
13
- font-size: 3rem;
14
- font-weight: bold;
34
+ min-height: 100vh;
35
+ color: var(--cream);
36
+ background: var(--ink);
37
+ font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
38
+ -webkit-font-smoothing: antialiased;
15
39
  }
16
- .container {
17
- width: 100vw;
40
+
41
+ .stone-welcome {
42
+ position: relative;
43
+ min-height: 100vh;
18
44
  display: flex;
45
+ flex-direction: column;
46
+ align-items: center;
19
47
  justify-content: center;
48
+ padding: 48px 24px;
49
+ background: var(--bg);
50
+ overflow: hidden;
51
+ }
52
+
53
+ /* Ember glow behind the mark. */
54
+ .stone-welcome .glow {
55
+ position: absolute;
56
+ top: 8%;
57
+ left: 50%;
58
+ width: 520px;
59
+ height: 520px;
60
+ transform: translateX(-50%);
61
+ background: radial-gradient(circle, rgba(255, 90, 31, 0.22) 0%, rgba(255, 90, 31, 0) 68%);
62
+ filter: blur(8px);
63
+ pointer-events: none;
64
+ z-index: 0;
20
65
  }
21
- .text-center {
66
+
67
+ .stone-welcome .hero {
68
+ position: relative;
69
+ z-index: 1;
70
+ width: 100%;
71
+ max-width: 680px;
22
72
  text-align: center;
73
+ animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
74
+ }
75
+
76
+ .stone-welcome .mark {
77
+ width: 104px;
78
+ height: 104px;
79
+ filter: drop-shadow(0 8px 28px rgba(255, 90, 31, 0.35));
80
+ }
81
+
82
+ .stone-welcome .eyebrow {
83
+ margin: 22px 0 0;
84
+ font-size: 12px;
85
+ font-weight: 600;
86
+ letter-spacing: 0.28em;
87
+ text-transform: uppercase;
88
+ color: var(--cream-dim);
89
+ }
90
+
91
+ .stone-welcome .title {
92
+ margin: 8px 0 0;
93
+ font-size: clamp(44px, 8vw, 76px);
94
+ font-weight: 800;
95
+ letter-spacing: -0.03em;
96
+ line-height: 1.02;
97
+ background: linear-gradient(92deg, var(--ember-1), var(--ember-2));
98
+ -webkit-background-clip: text;
99
+ background-clip: text;
100
+ color: transparent;
101
+ }
102
+
103
+ .stone-welcome .lead {
104
+ margin: 18px auto 0;
105
+ font-size: 20px;
106
+ font-weight: 600;
107
+ color: var(--cream);
108
+ }
109
+
110
+ .stone-welcome .tagline {
111
+ margin: 10px auto 0;
112
+ max-width: 46ch;
113
+ font-size: 16px;
114
+ line-height: 1.6;
115
+ color: var(--cream-dim);
116
+ }
117
+
118
+ .stone-welcome .links {
119
+ margin-top: 34px;
120
+ display: flex;
121
+ flex-wrap: wrap;
122
+ gap: 12px;
123
+ justify-content: center;
124
+ }
125
+
126
+ .stone-welcome .links a,
127
+ .stone-welcome .links .edit {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: 8px;
131
+ padding: 11px 18px;
132
+ border: 1px solid var(--line);
133
+ border-radius: 12px;
134
+ background: var(--card);
135
+ color: var(--cream);
136
+ font-size: 14px;
137
+ font-weight: 600;
138
+ text-decoration: none;
139
+ transition: border-color 0.2s, transform 0.2s, background 0.2s;
140
+ }
141
+
142
+ .stone-welcome .links a:hover {
143
+ transform: translateY(-2px);
144
+ border-color: rgba(255, 90, 31, 0.6);
145
+ background: rgba(255, 90, 31, 0.08);
146
+ }
147
+
148
+ .stone-welcome .links .edit {
149
+ font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;
150
+ color: var(--cream-dim);
151
+ }
152
+ .stone-welcome .links .edit b { color: var(--ember-1); font-weight: 700; }
153
+
154
+ .stone-welcome .brand {
155
+ position: relative;
156
+ z-index: 1;
157
+ margin-top: 56px;
158
+ font-size: 13px;
159
+ letter-spacing: 0.02em;
160
+ color: var(--cream-dim);
161
+ }
162
+ .stone-welcome .brand .dot { color: var(--ember-2); }
163
+
164
+ @keyframes rise {
165
+ from { opacity: 0; transform: translateY(14px); }
166
+ to { opacity: 1; transform: translateY(0); }
167
+ }
168
+
169
+ @media (prefers-reduced-motion: reduce) {
170
+ .stone-welcome .hero { animation: none; }
23
171
  }
24
- .mt-64 {
25
- margin-top: 64px;
26
- }
@@ -20,23 +20,26 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/use-react": "^0.8.4",
24
- "@stone-js/browser-adapter": "^0.8.4",
25
- "@stone-js/pipeline": "^0.8.4",
26
- "@stone-js/http-core": "^0.8.4",
27
- "@stone-js/node-http-adapter": "^0.8.4",
28
- "@stone-js/router": "^0.8.4",
29
- "@stone-js/core": "^0.8.4"
23
+ "@stone-js/use-react": "^0.8.6",
24
+ "@stone-js/browser-adapter": "^0.8.6",
25
+ "@stone-js/pipeline": "^0.8.6",
26
+ "@stone-js/http-core": "^0.8.6",
27
+ "@stone-js/node-http-adapter": "^0.8.6",
28
+ "@stone-js/router": "^0.8.6",
29
+ "@stone-js/core": "^0.8.6"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@babel/plugin-proposal-decorators": "^7.25.9",
33
33
  "@babel/preset-env": "^7.26.9",
34
34
  "@babel/preset-typescript": "^7.27.0",
35
- "@stone-js/cli": "^0.8.4",
35
+ "@stone-js/cli": "^0.8.6",
36
36
  "@types/node": "^24.0.7",
37
37
  "@types/react": "^19.0.7",
38
38
  "@types/react-dom": "^19.0.3",
39
39
  "@vitest/coverage-v8": "^3.0.9",
40
40
  "vitest": "^3.0.9"
41
+ },
42
+ "overrides": {
43
+ "glob": "^13.0.0"
41
44
  }
42
45
  }
@@ -65,5 +65,7 @@ describe('WelcomePage', () => {
65
65
  // Assert
66
66
  expect(response).toContain('/logo.svg')
67
67
  expect(response).toContain('alt="Stone.js"')
68
+ expect(response).toContain('Welcome')
69
+ expect(response).toContain('Stone.js')
68
70
  })
69
71
  })
@@ -1,6 +1,7 @@
1
1
  import { JSX } from 'react'
2
+ import { Promiseable } from '@stone-js/core'
2
3
  import { WelcomeService } from '../../services/WelcomeService'
3
- import { definePage, IPage, PageRenderContext, ReactIncomingEvent } from '@stone-js/use-react'
4
+ import { definePage, IPage, PageRenderContext, PageHeadContext, HeadContext, ReactIncomingEvent } from '@stone-js/use-react'
4
5
 
5
6
  /**
6
7
  * WelcomePage Options
@@ -30,6 +31,22 @@ export const WelcomePage = ({ welcomeService }: WelcomePageOptions): IPage<React
30
31
  return welcomeService.welcome(event.get<string>('name', 'World'))
31
32
  },
32
33
 
34
+ /**
35
+ * Set the page head tags, like title, meta, link, script, style.
36
+ *
37
+ * @returns The head context.
38
+ */
39
+ head ({ event }: PageHeadContext): Promiseable<HeadContext> {
40
+ return {
41
+ title: `${String(event.get<string>('name', 'World'))} · Welcome to Stone.js`,
42
+ description: 'A universal Stone.js application. Write your domain once; Stone.js is the context that runs it anywhere.',
43
+ metas: [
44
+ { name: 'author', content: 'Stone.js' },
45
+ { name: 'keywords', content: 'stonejs,continuum,react,universal,framework' }
46
+ ]
47
+ }
48
+ },
49
+
33
50
  /**
34
51
  * Render the component.
35
52
  *
@@ -37,10 +54,25 @@ export const WelcomePage = ({ welcomeService }: WelcomePageOptions): IPage<React
37
54
  */
38
55
  render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
39
56
  return (
40
- <section className='container'>
41
- <img src='/logo.svg' alt='Stone.js' width={96} height={96} />
42
- <h1 className='h1 text-center mt-64'>{data?.message}</h1>
43
- </section>
57
+ <main className='stone-welcome'>
58
+ <div className='glow' aria-hidden='true' />
59
+ <section className='hero'>
60
+ <img className='mark' src='/logo.svg' alt='Stone.js' width={104} height={104} />
61
+ <p className='eyebrow'>Welcome to</p>
62
+ <h1 className='title'>Stone.js</h1>
63
+ <p className='lead'>{data?.message}</p>
64
+ <p className='tagline'>
65
+ Your app is running. Write your domain once, Stone.js is the context that runs it
66
+ anywhere: server, serverless, browser, CLI and the edge.
67
+ </p>
68
+ <nav className='links'>
69
+ <a href='https://stonejs.dev/docs' target='_blank' rel='noreferrer noopener'>Documentation</a>
70
+ <a href='https://github.com/stone-foundation/stone-js-framework' target='_blank' rel='noreferrer noopener'>GitHub</a>
71
+ <span className='edit'>Edit <b>app/pages/welcome/WelcomePage.tsx</b></span>
72
+ </nav>
73
+ </section>
74
+ <footer className='brand'><span className='dot'>●</span> Stone.js — the continuum framework</footer>
75
+ </main>
44
76
  )
45
77
  }
46
78
  })