@stone-js/starters 0.8.3 → 0.8.4

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 (65) hide show
  1. package/basic-react-declarative/app/Application.tsx +1 -0
  2. package/basic-react-declarative/package.json +5 -5
  3. package/basic-react-declarative/public/logo.svg +16 -0
  4. package/basic-react-declarative/tests/Application.spec.ts +11 -2
  5. package/basic-react-imperative/app/Application.tsx +1 -0
  6. package/basic-react-imperative/package.json +5 -5
  7. package/basic-react-imperative/public/logo.svg +16 -0
  8. package/basic-react-imperative/tests/Application.spec.ts +11 -2
  9. package/basic-service-declarative/package.json +4 -4
  10. package/basic-service-imperative/package.json +4 -4
  11. package/continuum-showcase/LICENSE +21 -0
  12. package/continuum-showcase/app/Application.ts +45 -0
  13. package/continuum-showcase/app/layouts/MainLayout.tsx +1 -2
  14. package/continuum-showcase/app/pages/BlogPostPage.tsx +1 -1
  15. package/continuum-showcase/app/pages/HomePage.tsx +2 -2
  16. package/continuum-showcase/assets/css/index.css +26 -0
  17. package/continuum-showcase/assets/images/logo.svg +16 -0
  18. package/continuum-showcase/package.json +22 -10
  19. package/continuum-showcase/tests/HomePage.spec.tsx +40 -0
  20. package/continuum-showcase/tsconfig.json +33 -0
  21. package/continuum-showcase/vitest.config.ts +31 -0
  22. package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
  23. package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  24. package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  25. package/full-react-declarative/package.json +10 -10
  26. package/full-react-declarative/public/logo.svg +16 -0
  27. package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
  28. package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
  29. package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
  30. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
  31. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
  32. package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  33. package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  34. package/full-react-imperative/package.json +11 -12
  35. package/full-react-imperative/public/logo.svg +16 -0
  36. package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
  37. package/full-service-declarative/package.json +9 -9
  38. package/full-service-declarative/tests/PostService.spec.ts +56 -0
  39. package/full-service-declarative/vitest.config.ts +1 -1
  40. package/full-service-imperative/package.json +9 -9
  41. package/full-service-imperative/tests/PostService.spec.ts +47 -0
  42. package/full-service-imperative/vitest.config.ts +1 -1
  43. package/package.json +1 -1
  44. package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +1 -0
  45. package/standard-react-declarative/package.json +8 -8
  46. package/standard-react-declarative/public/logo.svg +16 -0
  47. package/standard-react-declarative/tests/WelcomePage.spec.ts +69 -0
  48. package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +1 -0
  49. package/standard-react-imperative/package.json +8 -8
  50. package/standard-react-imperative/public/logo.svg +16 -0
  51. package/standard-react-imperative/tests/WelcomePage.spec.ts +51 -0
  52. package/standard-service-declarative/package.json +6 -6
  53. package/standard-service-declarative/tests/Application.spec.ts +91 -0
  54. package/standard-service-declarative/vitest.config.ts +1 -1
  55. package/standard-service-imperative/package.json +6 -6
  56. package/standard-service-imperative/tests/Application.spec.ts +75 -0
  57. package/standard-service-imperative/vitest.config.ts +1 -1
  58. package/basic-react-declarative/public/logo.png +0 -0
  59. package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  60. package/basic-react-imperative/public/logo.png +0 -0
  61. package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  62. package/full-react-declarative/assets/img/logo.png +0 -0
  63. package/full-react-declarative/public/logo.png +0 -0
  64. package/full-react-imperative/assets/img/logo.png +0 -0
  65. package/full-react-imperative/public/logo.png +0 -0
@@ -75,6 +75,7 @@ export class Application implements IPage<ReactIncomingEvent> {
75
75
  render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
76
76
  return (
77
77
  <section className='container'>
78
+ <img src='/logo.svg' alt='Stone.js' width={96} height={96} />
78
79
  <h1 className='h1 text-center mt-64'>{data?.message}</h1>
79
80
  </section>
80
81
  )
@@ -20,16 +20,16 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.3",
24
- "@stone-js/use-react": "^0.8.3",
25
- "@stone-js/browser-adapter": "^0.8.3",
26
- "@stone-js/node-cli-adapter": "^0.8.3"
23
+ "@stone-js/core": "^0.8.4",
24
+ "@stone-js/use-react": "^0.8.4",
25
+ "@stone-js/browser-adapter": "^0.8.4",
26
+ "@stone-js/node-cli-adapter": "^0.8.4"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@babel/plugin-proposal-decorators": "^7.25.9",
30
30
  "@babel/preset-env": "^7.26.9",
31
31
  "@babel/preset-typescript": "^7.27.0",
32
- "@stone-js/cli": "^0.8.3",
32
+ "@stone-js/cli": "^0.8.4",
33
33
  "@types/node": "^24.0.7",
34
34
  "@types/react": "^19.0.7",
35
35
  "@types/react-dom": "^19.0.3",
@@ -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>
@@ -83,7 +83,7 @@ describe('Application', () => {
83
83
  expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
84
84
  })
85
85
 
86
- it('should render component', () => {
86
+ it('should render the message', () => {
87
87
  // Arrange
88
88
  const message = 'Hello World!'
89
89
 
@@ -91,6 +91,15 @@ describe('Application', () => {
91
91
  const response = renderToString(app.render({ data: { message } } as any))
92
92
 
93
93
  // Assert
94
- expect(response).toMatchSnapshot()
94
+ expect(response).toContain(message)
95
+ })
96
+
97
+ it('should render the Stone.js logo', () => {
98
+ // Act
99
+ const response = renderToString(app.render({ data: { message: 'Hello World!' } } as any))
100
+
101
+ // Assert
102
+ expect(response).toContain('/logo.svg')
103
+ expect(response).toContain('alt="Stone.js"')
95
104
  })
96
105
  })
@@ -44,6 +44,7 @@ export const FactoryHandler = ({ logger }: AppOptions): IPage<ReactIncomingEvent
44
44
  render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
45
45
  return (
46
46
  <section className='container'>
47
+ <img src='/logo.svg' alt='Stone.js' width={96} height={96} />
47
48
  <h1 className='h1 text-center mt-64'>{data?.message}</h1>
48
49
  </section>
49
50
  )
@@ -20,16 +20,16 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.3",
24
- "@stone-js/use-react": "^0.8.3",
25
- "@stone-js/browser-adapter": "^0.8.3",
26
- "@stone-js/node-cli-adapter": "^0.8.3"
23
+ "@stone-js/core": "^0.8.4",
24
+ "@stone-js/use-react": "^0.8.4",
25
+ "@stone-js/browser-adapter": "^0.8.4",
26
+ "@stone-js/node-cli-adapter": "^0.8.4"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@babel/plugin-proposal-decorators": "^7.25.9",
30
30
  "@babel/preset-env": "^7.26.9",
31
31
  "@babel/preset-typescript": "^7.27.0",
32
- "@stone-js/cli": "^0.8.3",
32
+ "@stone-js/cli": "^0.8.4",
33
33
  "@types/node": "^24.0.7",
34
34
  "@types/react": "^19.0.7",
35
35
  "@types/react-dom": "^19.0.3",
@@ -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>
@@ -55,7 +55,7 @@ describe('Application', () => {
55
55
  expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
56
56
  })
57
57
 
58
- it('should render component', () => {
58
+ it('should render the message', () => {
59
59
  // Arrange
60
60
  const message = 'Hello World!'
61
61
 
@@ -63,7 +63,16 @@ describe('Application', () => {
63
63
  const response = renderToString(app.render({ data: { message } } as any))
64
64
 
65
65
  // Assert
66
- expect(response).toMatchSnapshot()
66
+ expect(response).toContain(message)
67
+ })
68
+
69
+ it('should render the Stone.js logo', () => {
70
+ // Act
71
+ const response = renderToString(app.render({ data: { message: 'Hello World!' } } as any))
72
+
73
+ // Assert
74
+ expect(response).toContain('/logo.svg')
75
+ expect(response).toContain('alt="Stone.js"')
67
76
  })
68
77
 
69
78
  it('should get config', () => {
@@ -18,15 +18,15 @@
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.4",
22
+ "@stone-js/node-cli-adapter": "^0.8.4",
23
+ "@stone-js/node-http-adapter": "^0.8.4"
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.4",
30
30
  "@types/node": "^24.0.7",
31
31
  "@vitest/coverage-v8": "^3.0.9",
32
32
  "vitest": "^3.0.9"
@@ -18,15 +18,15 @@
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.4",
22
+ "@stone-js/node-cli-adapter": "^0.8.4",
23
+ "@stone-js/node-http-adapter": "^0.8.4"
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.4",
30
30
  "@types/node": "^24.0.7",
31
31
  "@vitest/coverage-v8": "^3.0.9",
32
32
  "vitest": "^3.0.9"
@@ -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,6 +1,5 @@
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, IPageLayout, PageLayoutRenderContext } from '@stone-js/use-react'
4
3
 
5
4
  /**
6
5
  * The default layout. In a future iteration (hierarchical heads), a layout may also
@@ -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,
@@ -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,
@@ -0,0 +1,26 @@
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;
9
+ }
10
+ .h1 {
11
+ margin: 0;
12
+ font-size: 3rem;
13
+ font-weight: 500;
14
+ font-weight: bold;
15
+ }
16
+ .container {
17
+ width: 100vw;
18
+ display: flex;
19
+ justify-content: center;
20
+ }
21
+ .text-center {
22
+ text-align: center;
23
+ }
24
+ .mt-64 {
25
+ margin-top: 64px;
26
+ }
@@ -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,35 @@
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.4",
19
+ "@stone-js/router": "^0.8.4",
20
+ "@stone-js/use-react": "^0.8.4",
21
+ "@stone-js/http-core": "^0.8.4",
22
+ "@stone-js/pipeline": "^0.8.4",
23
+ "@stone-js/browser-adapter": "^0.8.4",
24
+ "@stone-js/node-http-adapter": "^0.8.4"
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.4",
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"
24
36
  }
25
37
  }
@@ -0,0 +1,40 @@
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 Stone.js logo is rendered (Vite inlines the small SVG as a data URI),
33
+ // alongside the title and the domain message.
34
+ expect(html).toContain('<img')
35
+ expect(html).toContain('image/svg+xml')
36
+ expect(html).toContain('alt="Stone.js"')
37
+ expect(html).toContain('Stone.js Showcase')
38
+ expect(html).toContain('Write your domain once')
39
+ })
40
+ })
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "experimentalDecorators": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+ "resolveJsonModule": true,
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "noUncheckedSideEffectImports": true,
25
+
26
+ "types": ["node", "vite/client", "vitest/globals"],
27
+ },
28
+ "include": [
29
+ "app",
30
+ "tests",
31
+ "vitest.config.ts"
32
+ ]
33
+ }
@@ -0,0 +1,31 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { defineConfig } from 'vitest/config'
3
+
4
+ export default defineConfig({
5
+ // Mirror the CLI's static-asset aliases (see stone.config.mjs) so component imports like
6
+ // `import logo from '@img/logo.svg'` also resolve under the test runner.
7
+ resolve: {
8
+ alias: {
9
+ '@img': fileURLToPath(new URL('./assets/images', import.meta.url)),
10
+ '@css': fileURLToPath(new URL('./assets/css', import.meta.url)),
11
+ '@assets': fileURLToPath(new URL('./assets', import.meta.url))
12
+ }
13
+ },
14
+ test: {
15
+ globals: true,
16
+ environment: 'node',
17
+ include: ['./tests/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
18
+ coverage: {
19
+ provider: 'v8',
20
+ include: ['app/**/*.ts', 'app/**/*.tsx'],
21
+ reporter: ['text', 'html'],
22
+ reportsDirectory: './coverage',
23
+ watermarks: {
24
+ statements: [60, 80],
25
+ functions: [60, 80],
26
+ branches: [60, 80],
27
+ lines: [60, 80]
28
+ }
29
+ }
30
+ }
31
+ })
@@ -48,7 +48,7 @@ export class AppLayout implements IPageLayout {
48
48
  <header>
49
49
  <p>
50
50
  <StoneLink to='/' className='logo'>
51
- <img src='/logo.png' alt='Stone.js Logo' />
51
+ <img src='/logo.svg' alt='Stone.js' width={96} height={96} />
52
52
  <span>Stone.js</span>
53
53
  </StoneLink>
54
54
  </p>
@@ -19,7 +19,7 @@ export class ErrorLayout implements IPageLayout {
19
19
  <header className='mt-64'>
20
20
  <div className='container'>
21
21
  <p className='pa-24'>
22
- <img src='/logo.png' className='logo' alt='Stone.js Logo' />
22
+ <img src='/logo.svg' className='logo' alt='Stone.js' />
23
23
  </p>
24
24
  </div>
25
25
  </header>
@@ -19,7 +19,7 @@ export class SecurityLayout implements IPageLayout {
19
19
  <header className='mt-64'>
20
20
  <div className='container'>
21
21
  <p className='pa-24'>
22
- <img src='/logo.png' className='logo' alt='Stone.js Logo' />
22
+ <img src='/logo.svg' className='logo' alt='Stone.js' />
23
23
  </p>
24
24
  </div>
25
25
  </header>
@@ -18,15 +18,15 @@
18
18
  "test:cvg": "npm run test -- --coverage"
19
19
  },
20
20
  "dependencies": {
21
- "@stone-js/env": "^0.8.3",
22
- "@stone-js/http-core": "^0.8.3",
23
- "@stone-js/node-http-adapter": "^0.8.3",
24
- "@stone-js/router": "^0.8.3",
25
- "@stone-js/filesystem": "^0.8.3",
26
- "@stone-js/core": "^0.8.3",
27
- "@stone-js/use-react": "^0.8.3",
28
- "@stone-js/browser-adapter": "^0.8.3",
29
- "@stone-js/node-cli-adapter": "^0.8.3",
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",
30
30
  "axios": "^1.7.9",
31
31
  "react": "^19.0.0",
32
32
  "react-dom": "^19.0.0"
@@ -35,7 +35,7 @@
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.3",
38
+ "@stone-js/cli": "^0.8.4",
39
39
  "@types/axios": "^0.14.4",
40
40
  "@types/node": "^24.0.7",
41
41
  "@types/react": "^19.0.7",
@@ -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>
@@ -0,0 +1,51 @@
1
+ import { renderToString } from 'react-dom/server'
2
+ import { AppLayout } from '../app/layout/AppLayout/AppLayout'
3
+
4
+ // We must mock decorators 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
+ PageLayout: vi.fn(() => vi.fn()),
10
+ StoneLink: ({ children }: any) => children,
11
+ StoneOutlet: ({ children }: any) => children
12
+ }
13
+ })
14
+
15
+ describe('AppLayout', () => {
16
+ let layout: AppLayout
17
+
18
+ beforeEach(() => {
19
+ layout = new AppLayout({} as any)
20
+ })
21
+
22
+ const render = (): string => {
23
+ const container = {
24
+ make: () => ({ getUser: () => undefined })
25
+ }
26
+ return renderToString(layout.render({ container, children: null } as any))
27
+ }
28
+
29
+ it('should create an App Layout instance', () => {
30
+ // Assert
31
+ expect(layout).toBeInstanceOf(AppLayout)
32
+ })
33
+
34
+ it('should render the real Stone.js logo', () => {
35
+ // Act
36
+ const html = render()
37
+
38
+ // Assert
39
+ expect(html).toContain('/logo.svg')
40
+ expect(html).toContain('alt="Stone.js"')
41
+ })
42
+
43
+ it('should render the unauthenticated header', () => {
44
+ // Act
45
+ const html = render()
46
+
47
+ // Assert
48
+ expect(html).toContain('Login')
49
+ expect(html).toContain('Stone.js')
50
+ })
51
+ })
@@ -27,7 +27,7 @@ export const LayoutHeader: FC<LayoutHeaderOptions> = ({ container }) => {
27
27
  <nav className='app-navbar'>
28
28
  <div className='header-left'>
29
29
  <StoneLink to='/' className='logo'>
30
- <img src='/logo.png' alt='Stone.js Logo' />
30
+ <img src='/logo.svg' alt='Stone.js' width={96} height={96} />
31
31
  <span>Stone.js</span>
32
32
  </StoneLink>
33
33
  </div>