@pantheon-systems/create-p1-starter-kit 0.4.1 → 0.4.2

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/lib/messages.js CHANGED
@@ -13,8 +13,9 @@ export function showSuccess(projectName, projectPath, packageManager) {
13
13
  console.log(` ${pc.dim('# Copy .env.example to .env and fill in your credentials:')}`);
14
14
  console.log(` ${pc.cyan('cp')} .env.example .env`);
15
15
  console.log(` ${pc.dim('# Edit .env with your PCC_SITE_ID and PCC_TOKEN')}\n`);
16
+ const devCmd = packageManager === 'npm' ? 'npm run dev' : `${packageManager} dev`;
16
17
  console.log(` ${pc.dim('# Start the dev server:')}`);
17
- console.log(` ${pc.cyan(`${packageManager} dev`)}\n`);
18
+ console.log(` ${pc.cyan(devCmd)}\n`);
18
19
  console.log(pc.bold('Happy building! 🚀\n'));
19
20
  }
20
21
 
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { showSuccess } from './messages.js';
3
+
4
+ describe('showSuccess', () => {
5
+ let output;
6
+
7
+ beforeEach(() => {
8
+ output = [];
9
+ vi.spyOn(console, 'log').mockImplementation((...args) => output.push(args.join(' ')));
10
+ });
11
+
12
+ it('shows "npm run dev" for npm users', () => {
13
+ showSuccess('my-app', '/tmp/my-app', 'npm');
14
+ const allOutput = output.join('\n');
15
+ expect(allOutput).toContain('npm run dev');
16
+ });
17
+
18
+ it('shows "pnpm dev" for pnpm users', () => {
19
+ showSuccess('my-app', '/tmp/my-app', 'pnpm');
20
+ const allOutput = output.join('\n');
21
+ expect(allOutput).toContain('pnpm dev');
22
+ });
23
+
24
+ it('shows "yarn dev" for yarn users', () => {
25
+ showSuccess('my-app', '/tmp/my-app', 'yarn');
26
+ const allOutput = output.join('\n');
27
+ expect(allOutput).toContain('yarn dev');
28
+ });
29
+ });
package/package.json CHANGED
@@ -1,15 +1,11 @@
1
1
  {
2
2
  "name": "@pantheon-systems/create-p1-starter-kit",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Scaffold a new P1 starter project",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-p1-starter-kit": "./index.js"
8
8
  },
9
- "scripts": {
10
- "build": "node scripts/build-template.js",
11
- "prepublishOnly": "npm run build"
12
- },
13
9
  "files": [
14
10
  "index.js",
15
11
  "lib/",
@@ -36,9 +32,16 @@
36
32
  "engines": {
37
33
  "node": ">=20.12.0"
38
34
  },
35
+ "devDependencies": {
36
+ "vitest": "^4.1.5"
37
+ },
39
38
  "dependencies": {
40
39
  "@clack/prompts": "^1.5.1",
41
40
  "picocolors": "^1.1.1"
42
41
  },
43
- "license": "MIT"
44
- }
42
+ "license": "MIT",
43
+ "scripts": {
44
+ "build": "node scripts/build-template.js",
45
+ "test": "vitest run"
46
+ }
47
+ }
@@ -6,5 +6,9 @@ NEXT_PUBLIC_CSS_BASE_URL=https://css.example.com
6
6
  NEXT_PUBLIC_CSS_SITE_ID=site-123
7
7
  P1_CSS_API_KEY=your-api-key
8
8
 
9
+ # --- P1 Admin Dashboard (optional) ---
10
+ # Override the default dashboard URL (https://content.pantheon.io)
11
+ # NEXT_PUBLIC_P1_ADMIN_DASHBOARD_URL=https://staging.content.pantheon.io
12
+
9
13
  # Branch is auto-detected (defaults to main) unless specified:
10
14
  # NEXT_PUBLIC_CSS_BRANCH_ID=branch-456
@@ -0,0 +1,11 @@
1
+ # @pantheon-systems/p1-starter
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [6650602]
8
+ - Updated dependencies [dc7cfd7]
9
+ - @pantheon-systems/puck-css@0.4.2
10
+ - @pantheon-systems/css-client@0.4.2
11
+ - @pantheon-systems/p1-next-sdk@0.4.2
@@ -4,6 +4,37 @@ import type { Data } from "@puckeditor/core";
4
4
  import { RenderClient } from "@pantheon-systems/puck-css";
5
5
  import config from "../../puck.config";
6
6
 
7
- export function Client({ data }: { data: Data }) {
8
- return <RenderClient config={config} data={data} />;
7
+ export function Client({
8
+ data,
9
+ pageMetadata,
10
+ }: {
11
+ data: Data;
12
+ pageMetadata?: {
13
+ route: string;
14
+ documentName?: string;
15
+ pageType?: "page" | "template" | "override";
16
+ };
17
+ }) {
18
+ return (
19
+ <>
20
+ <RenderClient config={config} data={data} />
21
+ {pageMetadata && (
22
+ <footer className="mt-16 border-t border-gray-200 py-4 text-center text-sm text-gray-500">
23
+ Rendered with{" "}
24
+ <span className="font-medium">
25
+ {pageMetadata.documentName || pageMetadata.route}
26
+ </span>{" "}
27
+ from{" "}
28
+ <span className="font-medium">
29
+ {pageMetadata.pageType === "page" && "page"}
30
+ {pageMetadata.pageType === "template" && "page template"}
31
+ {pageMetadata.pageType === "override" && "page template override"}
32
+ {!pageMetadata.pageType && "page"}
33
+ </span>{" "}
34
+ at route{" "}
35
+ <span className="font-mono text-xs">{pageMetadata.route}</span>
36
+ </footer>
37
+ )}
38
+ </>
39
+ );
9
40
  }
@@ -125,7 +125,16 @@ export default async function Page({
125
125
  });
126
126
  const resolvedData = await resolveDataTemplates(data, context);
127
127
 
128
- return <Client data={resolvedData} />;
128
+ return (
129
+ <Client
130
+ data={resolvedData}
131
+ pageMetadata={{
132
+ route: path,
133
+ documentName: data.root.props?.title as string | undefined,
134
+ pageType: "page",
135
+ }}
136
+ />
137
+ );
129
138
  }
130
139
 
131
140
  export const dynamic = "force-dynamic";
@@ -7,7 +7,7 @@ export default function RootLayout({
7
7
  }) {
8
8
  return (
9
9
  <html lang="en">
10
- <body>{children}</body>
10
+ <body data-rm-theme="light">{children}</body>
11
11
  </html>
12
12
  );
13
13
  }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useCallback } from "react";
3
+ import React, { useCallback } from "react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import { Puck } from "@puckeditor/core";
6
6
  import {
@@ -31,6 +31,9 @@ try {
31
31
  const editorConfig = wrapConfigForEditorPreview(config);
32
32
 
33
33
  export function EditorClientWrapper({ path }: { path: string }) {
34
+ // Keep ref at this level - EditorClientWrapper doesn't unmount on branch switch
35
+ const lastGoodStateRef = React.useRef<{ puckKey: string; puckProps: any } | null>(null);
36
+
34
37
  if (!p1Config) {
35
38
  return (
36
39
  <div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
@@ -53,14 +56,20 @@ export function EditorClientWrapper({ path }: { path: string }) {
53
56
  config={p1Config}
54
57
  loginPageProps={{ title: "P1 Starter", subtitle: "Sign in to edit" }}
55
58
  >
56
- <EditorContent path={path} />
59
+ <EditorContent path={path} lastGoodStateRef={lastGoodStateRef} />
57
60
  </P1App>
58
61
  </P1NextRouterProvider>
59
62
  </P1QueryProvider>
60
63
  );
61
64
  }
62
65
 
63
- function EditorContent({ path }: { path: string }) {
66
+ function EditorContent({
67
+ path,
68
+ lastGoodStateRef,
69
+ }: {
70
+ path: string;
71
+ lastGoodStateRef: React.MutableRefObject<{ puckKey: string; puckProps: any } | null>;
72
+ }) {
64
73
  const router = useRouter();
65
74
  const p1Plugins = useP1Plugins(path, config);
66
75
 
@@ -78,6 +87,8 @@ function EditorContent({ path }: { path: string }) {
78
87
  pluginOptions: {
79
88
  onDocumentSelect: handleDocumentSelect,
80
89
  selectedDocumentPath: path,
90
+ siteId: process.env.NEXT_PUBLIC_CSS_SITE_ID,
91
+ dashboardUrl: process.env.NEXT_PUBLIC_P1_ADMIN_DASHBOARD_URL,
81
92
  },
82
93
  overrideOptions: {
83
94
  showDefaultPublish: false,
@@ -90,7 +101,15 @@ function EditorContent({ path }: { path: string }) {
90
101
  },
91
102
  });
92
103
 
93
- if (loading) {
104
+ // Update last good state when loading completes successfully (ref passed from parent)
105
+ React.useEffect(() => {
106
+ if (!loading && !error) {
107
+ lastGoodStateRef.current = { puckKey, puckProps };
108
+ }
109
+ }, [loading, error, puckKey, puckProps]);
110
+
111
+ // Show full loading screen only on first load (no previous state)
112
+ if (loading && !lastGoodStateRef.current) {
94
113
  return (
95
114
  <div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
96
115
  Loading editor...
@@ -98,7 +117,8 @@ function EditorContent({ path }: { path: string }) {
98
117
  );
99
118
  }
100
119
 
101
- if (error) {
120
+ // Show error only if we have no previous state to fall back to
121
+ if (error && !lastGoodStateRef.current) {
102
122
  return (
103
123
  <div style={{ textAlign: "center", padding: "4rem", fontFamily: "system-ui" }}>
104
124
  <h3>Error loading document</h3>
@@ -107,10 +127,55 @@ function EditorContent({ path }: { path: string }) {
107
127
  );
108
128
  }
109
129
 
130
+ // Use current state if loaded, otherwise keep showing last good state
131
+ const displayState = (!loading && !error)
132
+ ? { puckKey, puckProps }
133
+ : lastGoodStateRef.current ?? { puckKey, puckProps };
134
+
110
135
  return (
111
- <div className="puck-editor-theme">
136
+ <div className="puck-editor-theme" style={{ position: "relative" }}>
137
+ {/* Loading overlay - shown during branch switch */}
138
+ {loading && lastGoodStateRef.current && (
139
+ <div
140
+ style={{
141
+ position: "fixed",
142
+ top: "50%",
143
+ left: "50%",
144
+ transform: "translate(-50%, -50%)",
145
+ zIndex: 9999,
146
+ background: "rgba(255, 255, 255, 0.95)",
147
+ padding: "1rem 2rem",
148
+ borderRadius: "8px",
149
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
150
+ fontFamily: "system-ui",
151
+ fontSize: "14px",
152
+ color: "#333",
153
+ fontWeight: 500,
154
+ display: "flex",
155
+ alignItems: "center",
156
+ gap: "0.75rem",
157
+ }}
158
+ >
159
+ <div
160
+ style={{
161
+ width: "16px",
162
+ height: "16px",
163
+ border: "2px solid #e0e0e0",
164
+ borderTopColor: "#2563eb",
165
+ borderRadius: "50%",
166
+ animation: "spin 0.6s linear infinite",
167
+ }}
168
+ />
169
+ Switching workstream...
170
+ <style>{`
171
+ @keyframes spin {
172
+ to { transform: rotate(360deg); }
173
+ }
174
+ `}</style>
175
+ </div>
176
+ )}
112
177
  {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
113
- <Puck key={puckKey} {...puckProps as any} _experimentalFullScreenCanvas={true} />
178
+ <Puck key={displayState.puckKey} {...displayState.puckProps as any} _experimentalFullScreenCanvas={true} />
114
179
  </div>
115
180
  );
116
181
  }
@@ -64,7 +64,16 @@ export default async function HomePage() {
64
64
  referencedDatasourceIds,
65
65
  });
66
66
  const resolvedData = await resolveDataTemplates(data, context);
67
- return <Client data={resolvedData} />;
67
+ return (
68
+ <Client
69
+ data={resolvedData}
70
+ pageMetadata={{
71
+ route: "/",
72
+ documentName: data.root.props?.title as string | undefined,
73
+ pageType: "page",
74
+ }}
75
+ />
76
+ );
68
77
  }
69
78
 
70
79
  const routes = await listRoutes();
@@ -9,10 +9,9 @@ export const puckRoot = {
9
9
  title: "My Puck Editor",
10
10
  },
11
11
  render: (props: { children?: ReactNode; title?: string }) => {
12
- const { children, title } = props;
12
+ const { children } = props;
13
13
  return (
14
14
  <div className="font-sans antialiased">
15
- <h1>{title}</h1>
16
15
  {children}
17
16
  </div>
18
17
  );
@@ -11,9 +11,9 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@pantheon-systems/cpub-react-sdk": "^5.2.1",
14
- "@pantheon-systems/css-client": "^0.4.1",
15
- "@pantheon-systems/p1-next-sdk": "^0.4.1",
16
- "@pantheon-systems/puck-css": "^0.4.1",
14
+ "@pantheon-systems/css-client": "^0.4.2",
15
+ "@pantheon-systems/p1-next-sdk": "^0.4.2",
16
+ "@pantheon-systems/puck-css": "^0.4.2",
17
17
  "@puckeditor/core": "^0.21.1",
18
18
  "@tailwindcss/postcss": "^4.2.2",
19
19
  "classnames": "^2.5.1",