@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
@@ -299,3 +299,170 @@ h1, h2, h3, h4, h5, h6 {
299
299
  background: #FEE2E2;
300
300
  border-left: 4px solid #EF4444;
301
301
  }
302
+
303
+ /**
304
+ * Stone.js brand welcome hero — "Obsidienne & Braise".
305
+ * Fully scoped under .stone-welcome so it never clashes with the app's styles.
306
+ * Theme-aware: dark by default, light under prefers-color-scheme: light.
307
+ */
308
+ :root {
309
+ --sw-ink: #14100E;
310
+ --sw-ink-2: #1E1815;
311
+ --sw-cream: #F4EFE6;
312
+ --sw-cream-dim: #B8AE9E;
313
+ --sw-ember-1: #FFC46B;
314
+ --sw-ember-2: #FF5A1F;
315
+ --sw-line: rgba(244, 239, 230, 0.10);
316
+ --sw-card: rgba(244, 239, 230, 0.04);
317
+ --sw-bg: radial-gradient(1200px 600px at 50% -10%, #241C17 0%, var(--sw-ink) 55%);
318
+ }
319
+
320
+ @media (prefers-color-scheme: light) {
321
+ :root {
322
+ --sw-ink: #F4EFE6;
323
+ --sw-ink-2: #ffffff;
324
+ --sw-cream: #1B1613;
325
+ --sw-cream-dim: #6B6154;
326
+ --sw-line: rgba(27, 22, 19, 0.10);
327
+ --sw-card: rgba(27, 22, 19, 0.03);
328
+ --sw-bg: radial-gradient(1200px 600px at 50% -10%, #FFFFFF 0%, #F1E9DC 60%);
329
+ }
330
+ }
331
+
332
+ .stone-welcome,
333
+ .stone-welcome * { box-sizing: border-box; }
334
+
335
+ .stone-welcome {
336
+ position: relative;
337
+ min-height: 100vh;
338
+ display: flex;
339
+ flex-direction: column;
340
+ align-items: center;
341
+ justify-content: center;
342
+ padding: 48px 24px;
343
+ color: var(--sw-cream);
344
+ background: var(--sw-bg);
345
+ font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
346
+ -webkit-font-smoothing: antialiased;
347
+ overflow: hidden;
348
+ }
349
+
350
+ /* Ember glow behind the mark. */
351
+ .stone-welcome .glow {
352
+ position: absolute;
353
+ top: 8%;
354
+ left: 50%;
355
+ width: 520px;
356
+ height: 520px;
357
+ transform: translateX(-50%);
358
+ background: radial-gradient(circle, rgba(255, 90, 31, 0.22) 0%, rgba(255, 90, 31, 0) 68%);
359
+ filter: blur(8px);
360
+ pointer-events: none;
361
+ z-index: 0;
362
+ }
363
+
364
+ .stone-welcome .hero {
365
+ position: relative;
366
+ z-index: 1;
367
+ width: 100%;
368
+ max-width: 680px;
369
+ text-align: center;
370
+ animation: sw-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
371
+ }
372
+
373
+ .stone-welcome .mark {
374
+ width: 104px;
375
+ height: 104px;
376
+ filter: drop-shadow(0 8px 28px rgba(255, 90, 31, 0.35));
377
+ }
378
+
379
+ .stone-welcome .eyebrow {
380
+ margin: 22px 0 0;
381
+ font-size: 12px;
382
+ font-weight: 600;
383
+ letter-spacing: 0.28em;
384
+ text-transform: uppercase;
385
+ color: var(--sw-cream-dim);
386
+ }
387
+
388
+ .stone-welcome .title {
389
+ margin: 8px 0 0;
390
+ font-size: clamp(44px, 8vw, 76px);
391
+ font-weight: 800;
392
+ letter-spacing: -0.03em;
393
+ line-height: 1.02;
394
+ background: linear-gradient(92deg, var(--sw-ember-1), var(--sw-ember-2));
395
+ -webkit-background-clip: text;
396
+ background-clip: text;
397
+ color: transparent;
398
+ }
399
+
400
+ .stone-welcome .lead {
401
+ margin: 18px auto 0;
402
+ font-size: 20px;
403
+ font-weight: 600;
404
+ color: var(--sw-cream);
405
+ }
406
+
407
+ .stone-welcome .tagline {
408
+ margin: 10px auto 0;
409
+ max-width: 46ch;
410
+ font-size: 16px;
411
+ line-height: 1.6;
412
+ color: var(--sw-cream-dim);
413
+ }
414
+
415
+ .stone-welcome .links {
416
+ margin-top: 34px;
417
+ display: flex;
418
+ flex-wrap: wrap;
419
+ gap: 12px;
420
+ justify-content: center;
421
+ }
422
+
423
+ .stone-welcome .links a,
424
+ .stone-welcome .links .edit {
425
+ display: inline-flex;
426
+ align-items: center;
427
+ gap: 8px;
428
+ padding: 11px 18px;
429
+ border: 1px solid var(--sw-line);
430
+ border-radius: 12px;
431
+ background: var(--sw-card);
432
+ color: var(--sw-cream);
433
+ font-size: 14px;
434
+ font-weight: 600;
435
+ text-decoration: none;
436
+ transition: border-color 0.2s, transform 0.2s, background 0.2s;
437
+ }
438
+
439
+ .stone-welcome .links a:hover {
440
+ transform: translateY(-2px);
441
+ border-color: rgba(255, 90, 31, 0.6);
442
+ background: rgba(255, 90, 31, 0.08);
443
+ }
444
+
445
+ .stone-welcome .links .edit {
446
+ font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;
447
+ color: var(--sw-cream-dim);
448
+ }
449
+ .stone-welcome .links .edit b { color: var(--sw-ember-1); font-weight: 700; }
450
+
451
+ .stone-welcome .brand {
452
+ position: relative;
453
+ z-index: 1;
454
+ margin-top: 56px;
455
+ font-size: 13px;
456
+ letter-spacing: 0.02em;
457
+ color: var(--sw-cream-dim);
458
+ }
459
+ .stone-welcome .brand .dot { color: var(--sw-ember-2); }
460
+
461
+ @keyframes sw-rise {
462
+ from { opacity: 0; transform: translateY(14px); }
463
+ to { opacity: 1; transform: translateY(0); }
464
+ }
465
+
466
+ @media (prefers-reduced-motion: reduce) {
467
+ .stone-welcome .hero { animation: none; }
468
+ }
@@ -18,15 +18,15 @@
18
18
  "test:cvg": "npm run test -- --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/env": "^0.8.4",
22
- "@stone-js/http-core": "^0.8.4",
23
- "@stone-js/node-http-adapter": "^0.8.4",
24
- "@stone-js/router": "^0.8.4",
25
- "@stone-js/filesystem": "^0.8.4",
26
- "@stone-js/core": "^0.8.4",
27
- "@stone-js/use-react": "^0.8.4",
28
- "@stone-js/browser-adapter": "^0.8.4",
29
- "@stone-js/node-cli-adapter": "^0.8.4",
21
+ "@stone-js/env": "^0.8.6",
22
+ "@stone-js/http-core": "^0.8.6",
23
+ "@stone-js/node-http-adapter": "^0.8.6",
24
+ "@stone-js/router": "^0.8.6",
25
+ "@stone-js/filesystem": "^0.8.6",
26
+ "@stone-js/core": "^0.8.6",
27
+ "@stone-js/use-react": "^0.8.6",
28
+ "@stone-js/browser-adapter": "^0.8.6",
29
+ "@stone-js/node-cli-adapter": "^0.8.6",
30
30
  "axios": "^1.7.9",
31
31
  "react": "^19.0.0",
32
32
  "react-dom": "^19.0.0"
@@ -35,12 +35,15 @@
35
35
  "@babel/plugin-proposal-decorators": "^7.25.9",
36
36
  "@babel/preset-env": "^7.26.9",
37
37
  "@babel/preset-typescript": "^7.27.0",
38
- "@stone-js/cli": "^0.8.4",
38
+ "@stone-js/cli": "^0.8.6",
39
39
  "@types/axios": "^0.14.4",
40
40
  "@types/node": "^24.0.7",
41
41
  "@types/react": "^19.0.7",
42
42
  "@types/react-dom": "^19.0.3",
43
43
  "@vitest/coverage-v8": "^3.0.9",
44
44
  "vitest": "^3.0.9"
45
+ },
46
+ "overrides": {
47
+ "glob": "^13.0.0"
45
48
  }
46
49
  }
@@ -0,0 +1,38 @@
1
+ import { renderToString } from 'react-dom/server'
2
+ import { HomePage } from '../app/pages/home/HomePage'
3
+
4
+ // We must mock the @Page decorator and router-bound components to lighten the test environment
5
+ vi.mock('@stone-js/use-react', async (importOriginal) => {
6
+ const actual: any = await importOriginal()
7
+ return {
8
+ ...actual,
9
+ Page: vi.fn(() => vi.fn()),
10
+ StoneLink: ({ children }: any) => children
11
+ }
12
+ })
13
+
14
+ describe('HomePage', () => {
15
+ const render = (): string => {
16
+ const page = new HomePage()
17
+ return renderToString(page.render())
18
+ }
19
+
20
+ it('should render the premium Stone.js welcome hero with the real logo', () => {
21
+ // Act
22
+ const html = render()
23
+
24
+ // Assert
25
+ expect(html).toContain('/logo.svg')
26
+ expect(html).toContain('alt="Stone.js"')
27
+ })
28
+
29
+ it('should render the Welcome to Stone.js headline', () => {
30
+ // Act
31
+ const html = render()
32
+
33
+ // Assert
34
+ expect(html).toContain('Welcome')
35
+ expect(html).toContain('Stone.js')
36
+ expect(html).toContain('stone-welcome')
37
+ })
38
+ })
@@ -52,5 +52,5 @@ export const AuthMiddleware = ({
52
52
  */
53
53
  export const AuthMiddlewareBlueprint = defineMiddleware(
54
54
  AuthMiddleware,
55
- { global: true, params: [['/login', '/register']], isFactory: true }
55
+ { global: true, params: [['/', '/login', '/register']], isFactory: true }
56
56
  )
@@ -1,29 +1,23 @@
1
1
  import { JSX } from 'react'
2
- import { createPost } from '../utils'
3
2
  import { Post } from '../../models/Post'
4
- import { ILogger } from '@stone-js/core'
5
- import { PostForm } from '../../components/PostForm/PostForm'
6
- import { PostWidget } from '../../components/PostWidget/PostWidget'
7
3
  import { IPostService } from '../../services/contracts/IPostService'
8
- import { IPage, ReactIncomingEvent, HeadContext, definePage, IRouter, PageRenderContext } from '@stone-js/use-react'
4
+ import { IPage, ReactIncomingEvent, HeadContext, definePage, StoneLink, PageRenderContext } from '@stone-js/use-react'
9
5
 
10
6
  /**
11
7
  * HomePage options.
12
8
  */
13
9
  export interface HomePageOptions {
14
- router: IRouter
15
- logger: ILogger
16
10
  postService: IPostService
17
11
  }
18
12
 
19
13
  /**
20
14
  * Home Page.
21
15
  */
22
- export const HomePage = ({ router, logger, postService }: HomePageOptions): IPage<ReactIncomingEvent> => ({
16
+ export const HomePage = ({ postService }: HomePageOptions): IPage<ReactIncomingEvent> => ({
23
17
  head (): HeadContext {
24
18
  return {
25
- title: 'Home',
26
- description: 'Welcome to the Stone.js timeline'
19
+ title: 'Welcome to Stone.js',
20
+ description: 'Stone.js timeline write your domain once, Stone.js is the context that runs it anywhere.'
27
21
  }
28
22
  },
29
23
 
@@ -34,7 +28,12 @@ export const HomePage = ({ router, logger, postService }: HomePageOptions): IPag
34
28
  * @returns List of posts.
35
29
  */
36
30
  async handle (event: ReactIncomingEvent): Promise<Post[]> {
37
- return await postService.list(event.get<number>('limit', 10))
31
+ try {
32
+ return await postService.list(event.get<number>('limit', 10))
33
+ } catch {
34
+ // The welcome hero is public: render gracefully even when the API is unreachable.
35
+ return []
36
+ }
38
37
  },
39
38
 
40
39
  /**
@@ -42,12 +41,29 @@ export const HomePage = ({ router, logger, postService }: HomePageOptions): IPag
42
41
  *
43
42
  * @returns The rendered component.
44
43
  */
45
- render ({ data = [], event, container }: PageRenderContext<Post[]>): JSX.Element {
44
+ render ({ data = [] }: PageRenderContext<Post[]>): JSX.Element {
45
+ const count = data.length
46
+ const lead = `Your timeline is live — ${count} ${count === 1 ? 'post' : 'posts'} published.`
46
47
  return (
47
- <section>
48
- <PostForm onSubmit={createPost.bind(this, router, logger, postService)} />
49
- <PostWidget items={data} event={event} container={container} />
50
- </section>
48
+ <main className='stone-welcome'>
49
+ <div className='glow' aria-hidden='true' />
50
+ <section className='hero'>
51
+ <img className='mark' src='/logo.svg' alt='Stone.js' width={104} height={104} />
52
+ <p className='eyebrow'>Welcome to</p>
53
+ <h1 className='title'>Stone.js</h1>
54
+ <p className='lead'>{lead}</p>
55
+ <p className='tagline'>
56
+ Write your domain once, Stone.js is the context that runs it anywhere:
57
+ server, serverless, browser, CLI and the edge.
58
+ </p>
59
+ <nav className='links'>
60
+ <StoneLink to='/users'>Browse Users</StoneLink>
61
+ <a href='https://stonejs.dev/docs' target='_blank' rel='noreferrer noopener'>Documentation</a>
62
+ <a href='https://github.com/stone-foundation/stone-js-framework' target='_blank' rel='noreferrer noopener'>GitHub</a>
63
+ </nav>
64
+ </section>
65
+ <footer className='brand'><span className='dot'>●</span> Stone.js — the continuum framework</footer>
66
+ </main>
51
67
  )
52
68
  }
53
69
  })
@@ -299,3 +299,170 @@ h1, h2, h3, h4, h5, h6 {
299
299
  background: #FEE2E2;
300
300
  border-left: 4px solid #EF4444;
301
301
  }
302
+
303
+ /**
304
+ * Stone.js brand welcome hero — "Obsidienne & Braise".
305
+ * Fully scoped under .stone-welcome so it never clashes with the app's styles.
306
+ * Theme-aware: dark by default, light under prefers-color-scheme: light.
307
+ */
308
+ :root {
309
+ --sw-ink: #14100E;
310
+ --sw-ink-2: #1E1815;
311
+ --sw-cream: #F4EFE6;
312
+ --sw-cream-dim: #B8AE9E;
313
+ --sw-ember-1: #FFC46B;
314
+ --sw-ember-2: #FF5A1F;
315
+ --sw-line: rgba(244, 239, 230, 0.10);
316
+ --sw-card: rgba(244, 239, 230, 0.04);
317
+ --sw-bg: radial-gradient(1200px 600px at 50% -10%, #241C17 0%, var(--sw-ink) 55%);
318
+ }
319
+
320
+ @media (prefers-color-scheme: light) {
321
+ :root {
322
+ --sw-ink: #F4EFE6;
323
+ --sw-ink-2: #ffffff;
324
+ --sw-cream: #1B1613;
325
+ --sw-cream-dim: #6B6154;
326
+ --sw-line: rgba(27, 22, 19, 0.10);
327
+ --sw-card: rgba(27, 22, 19, 0.03);
328
+ --sw-bg: radial-gradient(1200px 600px at 50% -10%, #FFFFFF 0%, #F1E9DC 60%);
329
+ }
330
+ }
331
+
332
+ .stone-welcome,
333
+ .stone-welcome * { box-sizing: border-box; }
334
+
335
+ .stone-welcome {
336
+ position: relative;
337
+ min-height: 100vh;
338
+ display: flex;
339
+ flex-direction: column;
340
+ align-items: center;
341
+ justify-content: center;
342
+ padding: 48px 24px;
343
+ color: var(--sw-cream);
344
+ background: var(--sw-bg);
345
+ font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
346
+ -webkit-font-smoothing: antialiased;
347
+ overflow: hidden;
348
+ }
349
+
350
+ /* Ember glow behind the mark. */
351
+ .stone-welcome .glow {
352
+ position: absolute;
353
+ top: 8%;
354
+ left: 50%;
355
+ width: 520px;
356
+ height: 520px;
357
+ transform: translateX(-50%);
358
+ background: radial-gradient(circle, rgba(255, 90, 31, 0.22) 0%, rgba(255, 90, 31, 0) 68%);
359
+ filter: blur(8px);
360
+ pointer-events: none;
361
+ z-index: 0;
362
+ }
363
+
364
+ .stone-welcome .hero {
365
+ position: relative;
366
+ z-index: 1;
367
+ width: 100%;
368
+ max-width: 680px;
369
+ text-align: center;
370
+ animation: sw-rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
371
+ }
372
+
373
+ .stone-welcome .mark {
374
+ width: 104px;
375
+ height: 104px;
376
+ filter: drop-shadow(0 8px 28px rgba(255, 90, 31, 0.35));
377
+ }
378
+
379
+ .stone-welcome .eyebrow {
380
+ margin: 22px 0 0;
381
+ font-size: 12px;
382
+ font-weight: 600;
383
+ letter-spacing: 0.28em;
384
+ text-transform: uppercase;
385
+ color: var(--sw-cream-dim);
386
+ }
387
+
388
+ .stone-welcome .title {
389
+ margin: 8px 0 0;
390
+ font-size: clamp(44px, 8vw, 76px);
391
+ font-weight: 800;
392
+ letter-spacing: -0.03em;
393
+ line-height: 1.02;
394
+ background: linear-gradient(92deg, var(--sw-ember-1), var(--sw-ember-2));
395
+ -webkit-background-clip: text;
396
+ background-clip: text;
397
+ color: transparent;
398
+ }
399
+
400
+ .stone-welcome .lead {
401
+ margin: 18px auto 0;
402
+ font-size: 20px;
403
+ font-weight: 600;
404
+ color: var(--sw-cream);
405
+ }
406
+
407
+ .stone-welcome .tagline {
408
+ margin: 10px auto 0;
409
+ max-width: 46ch;
410
+ font-size: 16px;
411
+ line-height: 1.6;
412
+ color: var(--sw-cream-dim);
413
+ }
414
+
415
+ .stone-welcome .links {
416
+ margin-top: 34px;
417
+ display: flex;
418
+ flex-wrap: wrap;
419
+ gap: 12px;
420
+ justify-content: center;
421
+ }
422
+
423
+ .stone-welcome .links a,
424
+ .stone-welcome .links .edit {
425
+ display: inline-flex;
426
+ align-items: center;
427
+ gap: 8px;
428
+ padding: 11px 18px;
429
+ border: 1px solid var(--sw-line);
430
+ border-radius: 12px;
431
+ background: var(--sw-card);
432
+ color: var(--sw-cream);
433
+ font-size: 14px;
434
+ font-weight: 600;
435
+ text-decoration: none;
436
+ transition: border-color 0.2s, transform 0.2s, background 0.2s;
437
+ }
438
+
439
+ .stone-welcome .links a:hover {
440
+ transform: translateY(-2px);
441
+ border-color: rgba(255, 90, 31, 0.6);
442
+ background: rgba(255, 90, 31, 0.08);
443
+ }
444
+
445
+ .stone-welcome .links .edit {
446
+ font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;
447
+ color: var(--sw-cream-dim);
448
+ }
449
+ .stone-welcome .links .edit b { color: var(--sw-ember-1); font-weight: 700; }
450
+
451
+ .stone-welcome .brand {
452
+ position: relative;
453
+ z-index: 1;
454
+ margin-top: 56px;
455
+ font-size: 13px;
456
+ letter-spacing: 0.02em;
457
+ color: var(--sw-cream-dim);
458
+ }
459
+ .stone-welcome .brand .dot { color: var(--sw-ember-2); }
460
+
461
+ @keyframes sw-rise {
462
+ from { opacity: 0; transform: translateY(14px); }
463
+ to { opacity: 1; transform: translateY(0); }
464
+ }
465
+
466
+ @media (prefers-reduced-motion: reduce) {
467
+ .stone-welcome .hero { animation: none; }
468
+ }
@@ -18,15 +18,15 @@
18
18
  "test:cvg": "npm run test -- --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/env": "^0.8.4",
22
- "@stone-js/http-core": "^0.8.4",
23
- "@stone-js/node-http-adapter": "^0.8.4",
24
- "@stone-js/router": "^0.8.4",
25
- "@stone-js/filesystem": "^0.8.4",
26
- "@stone-js/core": "^0.8.4",
27
- "@stone-js/use-react": "^0.8.4",
28
- "@stone-js/browser-adapter": "^0.8.4",
29
- "@stone-js/node-cli-adapter": "^0.8.4",
21
+ "@stone-js/env": "^0.8.6",
22
+ "@stone-js/http-core": "^0.8.6",
23
+ "@stone-js/node-http-adapter": "^0.8.6",
24
+ "@stone-js/router": "^0.8.6",
25
+ "@stone-js/filesystem": "^0.8.6",
26
+ "@stone-js/core": "^0.8.6",
27
+ "@stone-js/use-react": "^0.8.6",
28
+ "@stone-js/browser-adapter": "^0.8.6",
29
+ "@stone-js/node-cli-adapter": "^0.8.6",
30
30
  "axios": "^1.7.9",
31
31
  "dayjs": "^1.11.13",
32
32
  "react": "^19.0.0",
@@ -36,12 +36,15 @@
36
36
  "@babel/plugin-proposal-decorators": "^7.25.9",
37
37
  "@babel/preset-env": "^7.26.9",
38
38
  "@babel/preset-typescript": "^7.27.0",
39
- "@stone-js/cli": "^0.8.4",
39
+ "@stone-js/cli": "^0.8.6",
40
40
  "@types/axios": "^0.14.4",
41
41
  "@types/node": "^24.0.7",
42
42
  "@types/react": "^19.0.7",
43
43
  "@types/react-dom": "^19.0.3",
44
44
  "@vitest/coverage-v8": "^3.0.9",
45
45
  "vitest": "^3.0.9"
46
+ },
47
+ "overrides": {
48
+ "glob": "^13.0.0"
46
49
  }
47
50
  }
@@ -0,0 +1,45 @@
1
+ import { renderToString } from 'react-dom/server'
2
+ import { HomePage } from '../app/pages/home/HomePage'
3
+
4
+ // We must mock router-bound components to lighten the test environment
5
+ vi.mock('@stone-js/use-react', async (importOriginal) => {
6
+ const actual: any = await importOriginal()
7
+ return {
8
+ ...actual,
9
+ StoneLink: ({ children }: any) => children
10
+ }
11
+ })
12
+
13
+ describe('HomePage', () => {
14
+ const render = (data: any[] = []): string => {
15
+ const page = HomePage({ postService: {} as any })
16
+ return renderToString(page.render?.({ data } as any))
17
+ }
18
+
19
+ it('should render the premium Stone.js welcome hero with the real logo', () => {
20
+ // Act
21
+ const html = render()
22
+
23
+ // Assert
24
+ expect(html).toContain('/logo.svg')
25
+ expect(html).toContain('alt="Stone.js"')
26
+ })
27
+
28
+ it('should render the Welcome to Stone.js headline', () => {
29
+ // Act
30
+ const html = render()
31
+
32
+ // Assert
33
+ expect(html).toContain('Welcome')
34
+ expect(html).toContain('Stone.js')
35
+ expect(html).toContain('stone-welcome')
36
+ })
37
+
38
+ it('should reflect the number of published posts in the lead', () => {
39
+ // Act
40
+ const html = render([{ id: 1 }, { id: 2 }])
41
+
42
+ // Assert
43
+ expect(html).toContain('2 posts')
44
+ })
45
+ })
@@ -0,0 +1,47 @@
1
+ import { EventHandler, Get } from '@stone-js/router'
2
+ import { IncomingHttpEvent, JsonHttpResponse } from '@stone-js/http-core'
3
+
4
+ /**
5
+ * Framework brand descriptor returned by the welcome endpoint.
6
+ */
7
+ export interface FrameworkBrand {
8
+ name: string
9
+ tagline: string
10
+ docs: string
11
+ github: string
12
+ }
13
+
14
+ /**
15
+ * Branded welcome payload.
16
+ */
17
+ export interface WelcomePayload {
18
+ message: string
19
+ framework: FrameworkBrand
20
+ }
21
+
22
+ /**
23
+ * Welcome Event Handler
24
+ *
25
+ * Greets the caller at the application root with a branded Stone.js payload.
26
+ * The `name` query param keeps the domain behavior meaningful.
27
+ */
28
+ @EventHandler('/', { name: 'welcome' })
29
+ export class WelcomeEventHandler {
30
+ /**
31
+ * Return the branded welcome payload.
32
+ */
33
+ @Get('/', { name: 'index' })
34
+ @JsonHttpResponse(200)
35
+ welcome (event: IncomingHttpEvent): WelcomePayload {
36
+ const name = event.get<string>('name', 'Stone')
37
+ return {
38
+ message: `Hello ${name}! Welcome to Stone.js.`,
39
+ framework: {
40
+ name: 'Stone.js',
41
+ tagline: 'The continuum framework',
42
+ docs: 'https://stonejs.dev',
43
+ github: 'https://github.com/stone-foundation/stone-js-framework'
44
+ }
45
+ }
46
+ }
47
+ }
@@ -14,7 +14,7 @@ export interface AuthMiddlewareOptions {
14
14
  */
15
15
  @Middleware({
16
16
  global: true,
17
- params: [['/login', '/register']]
17
+ params: [['/', '/login', '/register']]
18
18
  })
19
19
  export class AuthMiddleware implements IMiddleware<IncomingHttpEvent, OutgoingHttpResponse> {
20
20
  private readonly securityService: SecurityService