@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.
- package/basic-react-declarative/app/Application.tsx +22 -6
- package/basic-react-declarative/assets/css/index.css +163 -18
- package/basic-react-declarative/package.json +8 -5
- package/basic-react-declarative/public/logo.svg +16 -0
- package/basic-react-declarative/tests/Application.spec.ts +13 -4
- package/basic-react-imperative/app/Application.tsx +22 -6
- package/basic-react-imperative/assets/css/index.css +163 -18
- package/basic-react-imperative/package.json +8 -5
- package/basic-react-imperative/public/logo.svg +16 -0
- package/basic-react-imperative/tests/Application.spec.ts +15 -4
- package/basic-service-declarative/app/Application.ts +18 -3
- package/basic-service-declarative/package.json +7 -4
- package/basic-service-declarative/tests/Application.spec.ts +3 -2
- package/basic-service-imperative/app/Application.ts +18 -3
- package/basic-service-imperative/package.json +7 -4
- package/basic-service-imperative/tests/Application.spec.ts +3 -2
- package/continuum-showcase/LICENSE +21 -0
- package/continuum-showcase/app/Application.ts +45 -0
- package/continuum-showcase/app/layouts/MainLayout.tsx +13 -8
- package/continuum-showcase/app/pages/BlogPostPage.tsx +6 -3
- package/continuum-showcase/app/pages/HomePage.tsx +21 -8
- package/continuum-showcase/assets/css/index.css +198 -0
- package/continuum-showcase/assets/images/logo.svg +16 -0
- package/continuum-showcase/package.json +28 -10
- package/continuum-showcase/tests/HomePage.spec.tsx +41 -0
- package/continuum-showcase/tsconfig.json +33 -0
- package/continuum-showcase/vitest.config.ts +31 -0
- package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
- package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
- package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
- package/full-react-declarative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-react-declarative/app/pages/home/HomePage.tsx +22 -21
- package/full-react-declarative/assets/css/index.css +167 -0
- package/full-react-declarative/package.json +13 -10
- package/full-react-declarative/public/logo.svg +16 -0
- package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
- package/full-react-declarative/tests/HomePage.spec.ts +38 -0
- package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
- package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
- package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
- package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
- package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
- package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
- package/full-react-imperative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-react-imperative/app/pages/home/HomePage.tsx +32 -16
- package/full-react-imperative/assets/css/index.css +167 -0
- package/full-react-imperative/package.json +14 -12
- package/full-react-imperative/public/logo.svg +16 -0
- package/full-react-imperative/tests/HomePage.spec.ts +45 -0
- package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
- package/full-service-declarative/app/handlers/WelcomeEventHandler.ts +47 -0
- package/full-service-declarative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-service-declarative/package.json +12 -9
- package/full-service-declarative/tests/PostService.spec.ts +56 -0
- package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +54 -0
- package/full-service-declarative/vitest.config.ts +1 -1
- package/full-service-imperative/app/handlers/WelcomeEventHandler.ts +51 -0
- package/full-service-imperative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-service-imperative/package.json +12 -9
- package/full-service-imperative/tests/PostService.spec.ts +47 -0
- package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +31 -0
- package/full-service-imperative/vitest.config.ts +1 -1
- package/package.json +1 -1
- package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +37 -4
- package/standard-react-declarative/assets/css/index.css +163 -18
- package/standard-react-declarative/package.json +11 -8
- package/standard-react-declarative/public/logo.svg +16 -0
- package/standard-react-declarative/tests/WelcomePage.spec.ts +71 -0
- package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +37 -4
- package/standard-react-imperative/assets/css/index.css +163 -18
- package/standard-react-imperative/package.json +11 -8
- package/standard-react-imperative/public/logo.svg +16 -0
- package/standard-react-imperative/tests/WelcomePage.spec.ts +53 -0
- package/standard-service-declarative/app/handlers/WelcomeEventHandler.ts +2 -2
- package/standard-service-declarative/app/services/WelcomeService.ts +28 -2
- package/standard-service-declarative/package.json +9 -6
- package/standard-service-declarative/tests/Application.spec.ts +100 -0
- package/standard-service-declarative/vitest.config.ts +1 -1
- package/standard-service-imperative/app/handlers/welcomeEventHandler.ts +2 -4
- package/standard-service-imperative/app/services/welcomeService.ts +28 -2
- package/standard-service-imperative/package.json +9 -6
- package/standard-service-imperative/tests/Application.spec.ts +84 -0
- package/standard-service-imperative/vitest.config.ts +1 -1
- package/basic-react-declarative/public/logo.png +0 -0
- package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
- package/basic-react-imperative/public/logo.png +0 -0
- package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
- package/full-react-declarative/assets/img/logo.png +0 -0
- package/full-react-declarative/public/logo.png +0 -0
- package/full-react-imperative/assets/img/logo.png +0 -0
- 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
|
|
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 {
|
|
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.
|
|
22
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
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.
|
|
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)
|
|
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
|
|
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 {
|
|
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.
|
|
22
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
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.
|
|
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(
|
|
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/
|
|
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
|
|
7
|
-
*
|
|
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=
|
|
14
|
-
<header
|
|
15
|
-
|
|
16
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
<
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
"@
|
|
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
|
+
})
|