@stone-js/starters 0.8.3 → 0.8.5

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 (91) hide show
  1. package/basic-react-declarative/app/Application.tsx +22 -6
  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/public/logo.svg +16 -0
  5. package/basic-react-declarative/tests/Application.spec.ts +13 -4
  6. package/basic-react-imperative/app/Application.tsx +22 -6
  7. package/basic-react-imperative/assets/css/index.css +163 -18
  8. package/basic-react-imperative/package.json +8 -5
  9. package/basic-react-imperative/public/logo.svg +16 -0
  10. package/basic-react-imperative/tests/Application.spec.ts +15 -4
  11. package/basic-service-declarative/app/Application.ts +18 -3
  12. package/basic-service-declarative/package.json +7 -4
  13. package/basic-service-declarative/tests/Application.spec.ts +3 -2
  14. package/basic-service-imperative/app/Application.ts +18 -3
  15. package/basic-service-imperative/package.json +7 -4
  16. package/basic-service-imperative/tests/Application.spec.ts +3 -2
  17. package/continuum-showcase/LICENSE +21 -0
  18. package/continuum-showcase/app/Application.ts +45 -0
  19. package/continuum-showcase/app/layouts/MainLayout.tsx +13 -8
  20. package/continuum-showcase/app/pages/BlogPostPage.tsx +6 -3
  21. package/continuum-showcase/app/pages/HomePage.tsx +21 -8
  22. package/continuum-showcase/assets/css/index.css +198 -0
  23. package/continuum-showcase/assets/images/logo.svg +16 -0
  24. package/continuum-showcase/package.json +28 -10
  25. package/continuum-showcase/tests/HomePage.spec.tsx +41 -0
  26. package/continuum-showcase/tsconfig.json +33 -0
  27. package/continuum-showcase/vitest.config.ts +31 -0
  28. package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
  29. package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  30. package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  31. package/full-react-declarative/app/middleware/AuthMiddleware.ts +1 -1
  32. package/full-react-declarative/app/pages/home/HomePage.tsx +22 -21
  33. package/full-react-declarative/assets/css/index.css +167 -0
  34. package/full-react-declarative/package.json +13 -10
  35. package/full-react-declarative/public/logo.svg +16 -0
  36. package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
  37. package/full-react-declarative/tests/HomePage.spec.ts +38 -0
  38. package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
  39. package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
  40. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
  41. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
  42. package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  43. package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  44. package/full-react-imperative/app/middleware/AuthMiddleware.ts +1 -1
  45. package/full-react-imperative/app/pages/home/HomePage.tsx +32 -16
  46. package/full-react-imperative/assets/css/index.css +167 -0
  47. package/full-react-imperative/package.json +14 -12
  48. package/full-react-imperative/public/logo.svg +16 -0
  49. package/full-react-imperative/tests/HomePage.spec.ts +45 -0
  50. package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
  51. package/full-service-declarative/app/handlers/WelcomeEventHandler.ts +47 -0
  52. package/full-service-declarative/app/middleware/AuthMiddleware.ts +1 -1
  53. package/full-service-declarative/package.json +12 -9
  54. package/full-service-declarative/tests/PostService.spec.ts +56 -0
  55. package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +54 -0
  56. package/full-service-declarative/vitest.config.ts +1 -1
  57. package/full-service-imperative/app/handlers/WelcomeEventHandler.ts +51 -0
  58. package/full-service-imperative/app/middleware/AuthMiddleware.ts +1 -1
  59. package/full-service-imperative/package.json +12 -9
  60. package/full-service-imperative/tests/PostService.spec.ts +47 -0
  61. package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +31 -0
  62. package/full-service-imperative/vitest.config.ts +1 -1
  63. package/package.json +1 -1
  64. package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +37 -4
  65. package/standard-react-declarative/assets/css/index.css +163 -18
  66. package/standard-react-declarative/package.json +11 -8
  67. package/standard-react-declarative/public/logo.svg +16 -0
  68. package/standard-react-declarative/tests/WelcomePage.spec.ts +71 -0
  69. package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +37 -4
  70. package/standard-react-imperative/assets/css/index.css +163 -18
  71. package/standard-react-imperative/package.json +11 -8
  72. package/standard-react-imperative/public/logo.svg +16 -0
  73. package/standard-react-imperative/tests/WelcomePage.spec.ts +53 -0
  74. package/standard-service-declarative/app/handlers/WelcomeEventHandler.ts +2 -2
  75. package/standard-service-declarative/app/services/WelcomeService.ts +28 -2
  76. package/standard-service-declarative/package.json +9 -6
  77. package/standard-service-declarative/tests/Application.spec.ts +100 -0
  78. package/standard-service-declarative/vitest.config.ts +1 -1
  79. package/standard-service-imperative/app/handlers/welcomeEventHandler.ts +2 -4
  80. package/standard-service-imperative/app/services/welcomeService.ts +28 -2
  81. package/standard-service-imperative/package.json +9 -6
  82. package/standard-service-imperative/tests/Application.spec.ts +84 -0
  83. package/standard-service-imperative/vitest.config.ts +1 -1
  84. package/basic-react-declarative/public/logo.png +0 -0
  85. package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  86. package/basic-react-imperative/public/logo.png +0 -0
  87. package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  88. package/full-react-declarative/assets/img/logo.png +0 -0
  89. package/full-react-declarative/public/logo.png +0 -0
  90. package/full-react-imperative/assets/img/logo.png +0 -0
  91. package/full-react-imperative/public/logo.png +0 -0
@@ -38,13 +38,22 @@ export class Application implements IEventHandler<IncomingEvent> {
38
38
  */
39
39
  handle (event: IncomingEvent): ResponseData {
40
40
  // Get the name from the event
41
- const message = `Hello ${String(event.get<string>('name', 'World'))}!`
41
+ const name = String(event.get<string>('name', 'World'))
42
+ const message = `Hello ${name}! Welcome to Stone.js.`
42
43
 
43
44
  // Log a message
44
45
  this.logger.info(message)
45
46
 
46
- // Return a JSON response
47
- return { message }
47
+ // Return a branded JSON response
48
+ return {
49
+ message,
50
+ framework: {
51
+ name: 'Stone.js',
52
+ tagline: 'The continuum framework',
53
+ docs: 'https://stonejs.dev',
54
+ github: 'https://github.com/stone-foundation/stone-js-framework'
55
+ }
56
+ }
48
57
  }
49
58
  }
50
59
 
@@ -53,4 +62,10 @@ export class Application implements IEventHandler<IncomingEvent> {
53
62
  */
54
63
  export interface ResponseData {
55
64
  message: string
65
+ framework: {
66
+ name: string
67
+ tagline: string
68
+ docs: string
69
+ github: string
70
+ }
56
71
  }
@@ -18,17 +18,20 @@
18
18
  "test:cvg": "npm run test -- --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/core": "^0.8.3",
22
- "@stone-js/node-cli-adapter": "^0.8.3",
23
- "@stone-js/node-http-adapter": "^0.8.3"
21
+ "@stone-js/core": "^0.8.5",
22
+ "@stone-js/node-cli-adapter": "^0.8.5",
23
+ "@stone-js/node-http-adapter": "^0.8.5"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@babel/plugin-proposal-decorators": "^7.25.9",
27
27
  "@babel/preset-env": "^7.26.9",
28
28
  "@babel/preset-typescript": "^7.27.0",
29
- "@stone-js/cli": "^0.8.3",
29
+ "@stone-js/cli": "^0.8.5",
30
30
  "@types/node": "^24.0.7",
31
31
  "@vitest/coverage-v8": "^3.0.9",
32
32
  "vitest": "^3.0.9"
33
+ },
34
+ "overrides": {
35
+ "glob": "^11.0.0"
33
36
  }
34
37
  }
@@ -47,14 +47,15 @@ describe('Application', () => {
47
47
 
48
48
  it('should handle incoming events', () => {
49
49
  // Arrange
50
- const expectedMessage = 'Hello World!'
50
+ const expectedMessage = 'Hello World! Welcome to Stone.js.'
51
51
  const event = { get: () => 'World' } as unknown as IncomingEvent
52
52
 
53
53
  // Act
54
- const response = app.handle(event) as { message: string }
54
+ const response = app.handle(event)
55
55
 
56
56
  // Assert
57
57
  expect(response.message).toBe(expectedMessage)
58
+ expect(response.framework.name).toBe('Stone.js')
58
59
  expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
59
60
  })
60
61
  })
@@ -8,13 +8,22 @@ import { defineStoneApp, defineConfig, IBlueprint, ILogger, IncomingEvent, Funct
8
8
  export const FactoryHandler = ({ logger }: AppOptions): FunctionalEventHandler<IncomingEvent, ResponseData> => {
9
9
  return (event: IncomingEvent): ResponseData => {
10
10
  // Get the name from the event
11
- const message = `Hello ${String(event.get<string>('name', 'World'))}!`
11
+ const name = String(event.get<string>('name', 'World'))
12
+ const message = `Hello ${name}! Welcome to Stone.js.`
12
13
 
13
14
  // Log a message
14
15
  logger.info(message)
15
16
 
16
- // Return a JSON response
17
- return { message }
17
+ // Return a branded JSON response
18
+ return {
19
+ message,
20
+ framework: {
21
+ name: 'Stone.js',
22
+ tagline: 'The continuum framework',
23
+ docs: 'https://stonejs.dev',
24
+ github: 'https://github.com/stone-foundation/stone-js-framework'
25
+ }
26
+ }
18
27
  }
19
28
  }
20
29
 
@@ -57,4 +66,10 @@ interface AppOptions {
57
66
  */
58
67
  export interface ResponseData {
59
68
  message: string
69
+ framework: {
70
+ name: string
71
+ tagline: string
72
+ docs: string
73
+ github: string
74
+ }
60
75
  }
@@ -18,17 +18,20 @@
18
18
  "test:cvg": "npm run test -- --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/core": "^0.8.3",
22
- "@stone-js/node-cli-adapter": "^0.8.3",
23
- "@stone-js/node-http-adapter": "^0.8.3"
21
+ "@stone-js/core": "^0.8.5",
22
+ "@stone-js/node-cli-adapter": "^0.8.5",
23
+ "@stone-js/node-http-adapter": "^0.8.5"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@babel/plugin-proposal-decorators": "^7.25.9",
27
27
  "@babel/preset-env": "^7.26.9",
28
28
  "@babel/preset-typescript": "^7.27.0",
29
- "@stone-js/cli": "^0.8.3",
29
+ "@stone-js/cli": "^0.8.5",
30
30
  "@types/node": "^24.0.7",
31
31
  "@vitest/coverage-v8": "^3.0.9",
32
32
  "vitest": "^3.0.9"
33
+ },
34
+ "overrides": {
35
+ "glob": "^11.0.0"
33
36
  }
34
37
  }
@@ -34,8 +34,9 @@ describe('Application', () => {
34
34
  // Act
35
35
  const response = await app(event)
36
36
  // Assert
37
- expect(response.message).toBe('Hello World!')
38
- expect(mockedLogger.info).toHaveBeenCalledWith('Hello World!')
37
+ expect(response.message).toBe('Hello World! Welcome to Stone.js.')
38
+ expect(response.framework.name).toBe('Stone.js')
39
+ expect(mockedLogger.info).toHaveBeenCalledWith('Hello World! Welcome to Stone.js.')
39
40
  })
40
41
 
41
42
  it('should get config', () => {
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Stone Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,45 @@
1
+ import { Routing } from '@stone-js/router'
2
+ import { UseReact } from '@stone-js/use-react'
3
+ import { Browser } from '@stone-js/browser-adapter'
4
+ import { NodeHttp } from '@stone-js/node-http-adapter'
5
+ import { StoneApp, AdapterHookListenerContext, Hook, LogLevel, Logger } from '@stone-js/core'
6
+
7
+ /**
8
+ * Application — the single entry point of the showcase.
9
+ *
10
+ * The whole point of the Continuum Architecture is here: you declare the CONTEXT once
11
+ * (the adapters + features below) and the same domain (the `@Page` handlers) runs under it,
12
+ * server-side and in the browser, unchanged.
13
+ *
14
+ * @NodeHttp() serves the pages over HTTP with server-side rendering.
15
+ * @Browser() re-hydrates and runs the very same pages client-side.
16
+ * @UseReact() plugs React in as the view engine.
17
+ * @Routing() enables the universal router the `@Page` classes register on.
18
+ * @StoneApp() bootstraps the application (required).
19
+ */
20
+ @Routing()
21
+ @Browser()
22
+ @UseReact()
23
+ @NodeHttp({ default: true })
24
+ @StoneApp({ name: 'Stone.js Showcase', logger: { level: LogLevel.INFO } })
25
+ export class Application {
26
+ /**
27
+ * Run before the application starts.
28
+ *
29
+ * @param blueprint - The blueprint.
30
+ */
31
+ @Hook('onStart')
32
+ onStart ({ blueprint }: AdapterHookListenerContext): void {
33
+ Logger.log(`${String(blueprint.get('stone.name', 'Stone App'))} is starting...`)
34
+ }
35
+
36
+ /**
37
+ * Run just before the application stops.
38
+ *
39
+ * @param blueprint - The blueprint.
40
+ */
41
+ @Hook('onStop')
42
+ onStop ({ blueprint }: AdapterHookListenerContext): void {
43
+ Logger.log(`${String(blueprint.get('stone.name', 'Stone App'))} is stopping...`)
44
+ }
45
+ }
@@ -1,19 +1,24 @@
1
1
  import React from 'react'
2
- import { PageLayout } from '@stone-js/router'
3
- import { IPageLayout, PageLayoutRenderContext } from '@stone-js/use-react'
2
+ import { PageLayout, StoneOutlet, IPageLayout, PageLayoutRenderContext } from '@stone-js/use-react'
4
3
 
5
4
  /**
6
- * The default layout. In a future iteration (hierarchical heads), a layout may also
7
- * contribute a base head via `createHead().merge(...)`.
5
+ * The default layout: a thin branded chrome (top bar + footer) around the page outlet.
6
+ * The page content is placed via <StoneOutlet>, never a bare {children}.
8
7
  */
9
8
  @PageLayout({ name: 'default' })
10
9
  export class MainLayout implements IPageLayout {
11
10
  render ({ children }: PageLayoutRenderContext) {
12
11
  return (
13
- <div className="app">
14
- <header><nav><a href="/">Home</a> · <a href="/blog/hello-world">Blog</a></nav></header>
15
- {children}
16
- <footer>◆ Stone.js — the continuum framework</footer>
12
+ <div className='cx-shell'>
13
+ <header className='cx-nav'>
14
+ <a className='cx-wordmark' href='/'><span className='dot'>●</span> Stone.js</a>
15
+ <nav>
16
+ <a href='/'>Home</a>
17
+ <a href='/blog/hello-world'>Blog</a>
18
+ </nav>
19
+ </header>
20
+ <StoneOutlet>{children}</StoneOutlet>
21
+ <footer className='cx-foot'>Built with Stone.js — the continuum framework</footer>
17
22
  </div>
18
23
  )
19
24
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
- import { Page } from '@stone-js/router'
3
2
  import {
3
+ Page,
4
4
  createHead,
5
5
  IPage,
6
6
  PageRenderContext,
@@ -20,7 +20,7 @@ interface Post {
20
20
  * Blog post page — demonstrates a fully dynamic, data-driven head built from the
21
21
  * loader (`handle`) output, including an `article` Open Graph type and per-post JSON-LD.
22
22
  */
23
- @Page({ path: '/blog/:slug' })
23
+ @Page('/blog/:slug')
24
24
  export class BlogPostPage implements IPage<ReactIncomingEvent> {
25
25
  handle (event: ReactIncomingEvent): Post {
26
26
  const slug = event.get<string>('slug', 'hello-world')
@@ -53,8 +53,11 @@ export class BlogPostPage implements IPage<ReactIncomingEvent> {
53
53
 
54
54
  render ({ data }: PageRenderContext<Post>) {
55
55
  return (
56
- <article>
56
+ <article className='cx-article'>
57
+ <a className='cx-back' href='/'>← Back</a>
58
+ <p className='cx-kicker'>Stone.js Blog</p>
57
59
  <h1>{data?.title}</h1>
60
+ <p className='cx-excerpt'>{data?.excerpt}</p>
58
61
  <p>{data?.body}</p>
59
62
  </article>
60
63
  )
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
- import { Page } from '@stone-js/router'
3
- import logo from '@img/logo.png'
2
+ import logo from '@img/logo.svg'
4
3
  import {
4
+ Page,
5
5
  createHead,
6
6
  IPage,
7
7
  PageRenderContext,
@@ -18,7 +18,7 @@ interface HomeData {
18
18
  * Twitter card and JSON-LD structured data), all from `head()`;
19
19
  * - importing a static asset via the `@img` alias (resolved by the CLI's Vite config).
20
20
  */
21
- @Page({ path: '/' })
21
+ @Page('/')
22
22
  export class HomePage implements IPage<ReactIncomingEvent> {
23
23
  handle (): HomeData {
24
24
  return { message: 'Write your domain once. Stone.js applies the context.' }
@@ -48,11 +48,24 @@ export class HomePage implements IPage<ReactIncomingEvent> {
48
48
 
49
49
  render ({ data }: PageRenderContext<HomeData>) {
50
50
  return (
51
- <main>
52
- <img src={logo} alt="Stone.js" width={96} height={96} />
53
- <h1>Stone.js Showcase</h1>
54
- <p>{data?.message}</p>
55
- </main>
51
+ <section className='stone-welcome'>
52
+ <div className='glow' aria-hidden='true' />
53
+ <div className='hero'>
54
+ <img className='mark' src={logo} alt='Stone.js' width={104} height={104} />
55
+ <p className='eyebrow'>The continuum showcase</p>
56
+ <h1 className='title'>Stone.js</h1>
57
+ <p className='lead'>{data?.message}</p>
58
+ <p className='tagline'>
59
+ One domain, rendered on the server and hydrated in the browser, with a fluent
60
+ head / OpenGraph API and static-asset aliases.
61
+ </p>
62
+ <nav className='links'>
63
+ <a href='/blog/hello-world'>Read the blog demo</a>
64
+ <a href='https://stonejs.dev/docs' target='_blank' rel='noreferrer noopener'>Documentation</a>
65
+ <span className='edit'>Edit <b>app/pages/HomePage.tsx</b></span>
66
+ </nav>
67
+ </div>
68
+ </section>
56
69
  )
57
70
  }
58
71
  }
@@ -0,0 +1,198 @@
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%);
16
+ }
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 {
33
+ margin: 0;
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;
39
+ }
40
+
41
+ .stone-welcome {
42
+ position: relative;
43
+ min-height: 100vh;
44
+ display: flex;
45
+ flex-direction: column;
46
+ align-items: center;
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;
65
+ }
66
+
67
+ .stone-welcome .hero {
68
+ position: relative;
69
+ z-index: 1;
70
+ width: 100%;
71
+ max-width: 680px;
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; }
171
+ }
172
+
173
+ /* --- Showcase chrome (MainLayout) --- */
174
+ .cx-shell { min-height: 100vh; display: flex; flex-direction: column; background: var(--bg); }
175
+ .cx-nav {
176
+ position: sticky; top: 0; z-index: 5;
177
+ display: flex; align-items: center; justify-content: space-between;
178
+ padding: 16px 24px; border-bottom: 1px solid var(--line);
179
+ backdrop-filter: blur(8px);
180
+ }
181
+ .cx-wordmark { font-weight: 800; letter-spacing: -0.02em; color: var(--cream); text-decoration: none; }
182
+ .cx-wordmark .dot { color: var(--ember-2); }
183
+ .cx-nav nav { display: flex; gap: 18px; }
184
+ .cx-nav nav a { color: var(--cream-dim); text-decoration: none; font-size: 14px; font-weight: 600; }
185
+ .cx-nav nav a:hover { color: var(--ember-1); }
186
+ .cx-foot { padding: 22px; text-align: center; color: var(--cream-dim); font-size: 13px; border-top: 1px solid var(--line); }
187
+
188
+ /* The hero fills the space between nav and footer rather than a full extra viewport. */
189
+ .cx-shell .stone-welcome { min-height: calc(100vh - 140px); }
190
+
191
+ /* --- Article (BlogPostPage) --- */
192
+ .cx-article { max-width: 720px; margin: 0 auto; padding: 64px 24px; color: var(--cream); }
193
+ .cx-back { color: var(--cream-dim); text-decoration: none; font-size: 14px; font-weight: 600; }
194
+ .cx-back:hover { color: var(--ember-1); }
195
+ .cx-kicker { margin: 28px 0 0; font-size: 12px; letter-spacing: 0.24em; text-transform: uppercase; color: var(--ember-2); font-weight: 700; }
196
+ .cx-article h1 { margin: 8px 0 0; font-size: clamp(32px, 5vw, 48px); letter-spacing: -0.02em; }
197
+ .cx-excerpt { margin-top: 12px; font-size: 19px; color: var(--cream-dim); line-height: 1.6; }
198
+ .cx-article > p:last-child { margin-top: 20px; font-size: 16px; line-height: 1.75; }
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
2
+ <title>Stone.js</title>
3
+ <style>
4
+ .ink { stroke: #1B1613; }
5
+ @media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
6
+ </style>
7
+ <defs>
8
+ <linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
9
+ <stop offset="0" stop-color="#FFC46B" />
10
+ <stop offset="1" stop-color="#FF5A1F" />
11
+ </linearGradient>
12
+ </defs>
13
+ <path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
14
+ <path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
15
+ <path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
16
+ </svg>
@@ -3,23 +3,41 @@
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "description": "Showcase app exercising the new head/OG API and static-asset aliases (2026-07 continuum pass).",
6
+ "description": "Continuum showcase: one domain rendered server-side and in the browser, with the fluent head/OpenGraph API and static-asset aliases.",
7
7
  "scripts": {
8
8
  "dev": "stone dev",
9
9
  "build": "stone build",
10
- "preview": "stone preview"
10
+ "build:ssr": "stone build --ssr",
11
+ "preview": "stone preview",
12
+ "test": "vitest run",
13
+ "test:cvg": "npm run test -- --coverage"
11
14
  },
12
15
  "dependencies": {
13
- "@stone-js/browser-adapter": "^0.8.3",
14
- "@stone-js/core": "^0.8.3",
15
- "@stone-js/node-http-adapter": "^0.8.3",
16
- "@stone-js/router": "^0.8.3",
17
- "@stone-js/use-react": "^0.8.3",
18
- "@stone-js/use-view": "^0.8.3",
19
16
  "react": "^19.0.0",
20
- "react-dom": "^19.0.0"
17
+ "react-dom": "^19.0.0",
18
+ "@stone-js/core": "^0.8.5",
19
+ "@stone-js/router": "^0.8.5",
20
+ "@stone-js/use-react": "^0.8.5",
21
+ "@stone-js/http-core": "^0.8.5",
22
+ "@stone-js/pipeline": "^0.8.5",
23
+ "@stone-js/browser-adapter": "^0.8.5",
24
+ "@stone-js/node-http-adapter": "^0.8.5"
21
25
  },
22
26
  "devDependencies": {
23
- "@stone-js/cli": "^0.8.3"
27
+ "@babel/plugin-proposal-decorators": "^7.25.9",
28
+ "@babel/preset-env": "^7.26.9",
29
+ "@babel/preset-typescript": "^7.27.0",
30
+ "@stone-js/cli": "^0.8.5",
31
+ "@types/node": "^24.0.7",
32
+ "@types/react": "^19.0.7",
33
+ "@types/react-dom": "^19.0.3",
34
+ "@vitest/coverage-v8": "^3.0.9",
35
+ "vitest": "^3.0.9"
36
+ },
37
+ "overrides": {
38
+ "glob": "^11.0.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=20.0.0"
24
42
  }
25
43
  }
@@ -0,0 +1,41 @@
1
+ import { renderToString } from 'react-dom/server'
2
+ import { HomePage } from '../app/pages/HomePage'
3
+
4
+ // Mock the page decorator so the class can be instantiated without a running app context.
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
+ }
11
+ })
12
+
13
+ describe('HomePage', () => {
14
+ it('returns the domain message from handle', () => {
15
+ // Arrange
16
+ const page = new HomePage()
17
+
18
+ // Act
19
+ const data = page.handle()
20
+
21
+ // Assert
22
+ expect(data.message).toBe('Write your domain once. Stone.js applies the context.')
23
+ })
24
+
25
+ it('renders the Stone.js logo and the message', () => {
26
+ // Arrange
27
+ const page = new HomePage()
28
+
29
+ // Act
30
+ const html = renderToString(page.render({ data: page.handle() } as any))
31
+
32
+ // Assert: the premium welcome hero renders the Stone.js logo (Vite inlines the small SVG
33
+ // as a data URI), the brand title and the domain message.
34
+ expect(html).toContain('stone-welcome')
35
+ expect(html).toContain('<img')
36
+ expect(html).toContain('image/svg+xml')
37
+ expect(html).toContain('alt="Stone.js"')
38
+ expect(html).toContain('Stone.js')
39
+ expect(html).toContain('Write your domain once')
40
+ })
41
+ })