rastack 0.0.56 → 0.0.58

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.0.58](https://github.com/theserverkid/reactapistack/compare/v0.0.57...v0.0.58) (2026-07-18)
6
+
7
+ ### [0.0.57](https://github.com/theserverkid/reactapistack/compare/v0.0.56...v0.0.57) (2026-07-18)
8
+
5
9
  ### [0.0.56](https://github.com/theserverkid/reactapistack/compare/v0.0.55...v0.0.56) (2026-07-18)
6
10
 
7
11
 
package/admin/index.tsx CHANGED
@@ -17,12 +17,12 @@ import React, { useEffect, useMemo, useState } from "react";
17
17
  import { createRoot } from "react-dom/client";
18
18
  import { RAStackProvider, useRastackClient } from "../provider";
19
19
  import type { RastackWasmModule } from "../provider/types";
20
- import { Spinner } from "../components";
21
20
  import { ThemeProvider } from "../theme";
22
21
  import { defineTheme } from "../tokens";
23
22
  import { AdminApp } from "./AdminApp";
24
23
  import { parseManifest, type AdminResource } from "./adapters";
25
24
  import { createHttpEngine } from "./httpEngine";
25
+ import { BootScreen } from "./ui/BootScreen";
26
26
 
27
27
  interface AdminConfig {
28
28
  mode: "local" | "remote";
@@ -55,22 +55,19 @@ const loadEngine = async (): Promise<RastackWasmModule> => {
55
55
  return mod;
56
56
  };
57
57
 
58
- function Loading({ label }: { label: string }): React.JSX.Element {
59
- return (
60
- <div className="ras-boot">
61
- <Spinner /> {label}
62
- </div>
63
- );
64
- }
65
-
66
58
  /** Local (WASM) admin: wait for the provider's engine, then render. */
67
59
  function LocalAdmin({ resources }: { resources: AdminResource[] }): React.JSX.Element {
68
60
  const client = useRastackClient();
69
61
  if (client.status === "error")
70
- return <Loading label="The WASM engine failed to start — check the console." />;
62
+ return (
63
+ <BootScreen
64
+ error
65
+ label="The WASM engine failed to start — check the console."
66
+ />
67
+ );
71
68
  if (!client.engine || client.status !== "ready")
72
69
  return (
73
- <Loading
70
+ <BootScreen
74
71
  label={
75
72
  client.status === "seeding"
76
73
  ? "Seeding the local warehouse…"
@@ -108,8 +105,17 @@ function Boot(): React.JSX.Element {
108
105
  }, []);
109
106
 
110
107
  if (error)
111
- return <div className="ras-boot">Failed to load the schema: {error}</div>;
112
- if (!resources || !manifest) return <Loading label="Loading the schema…" />;
108
+ return (
109
+ <ThemeProvider tokens={theme}>
110
+ <BootScreen error label={`Failed to load the schema: ${error}`} />
111
+ </ThemeProvider>
112
+ );
113
+ if (!resources || !manifest)
114
+ return (
115
+ <ThemeProvider tokens={theme}>
116
+ <BootScreen label="Loading the schema…" />
117
+ </ThemeProvider>
118
+ );
113
119
 
114
120
  if (cfg.mode === "remote") {
115
121
  return (
@@ -0,0 +1,201 @@
1
+ /**
2
+ * The admin's boot / loading screen — what you see while the schema loads and
3
+ * the WASM engine spins up. It is deliberately **self-contained**: the main
4
+ * `AdminStyles` sheet is only injected once `<AdminApp>` mounts, so this screen
5
+ * ships its own scoped `<style>` (guarded against double-injection) and leans on
6
+ * the design-token CSS variables with graceful fallbacks, so it looks right in
7
+ * every phase — before the `<ThemeProvider>` is even in the tree, and in both
8
+ * light and dark. It reuses the product `<BrandMark>` and honours
9
+ * `prefers-reduced-motion`.
10
+ */
11
+ import React from "react";
12
+ import { BrandMark } from "./icons";
13
+
14
+ const STYLE_ID = "ras-boot-style";
15
+
16
+ const BOOT_CSS = String.raw`
17
+ .ras-loader {
18
+ min-height: 100vh;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: 24px;
23
+ background:
24
+ radial-gradient(120% 90% at 50% -10%,
25
+ color-mix(in srgb, var(--color-accent, #4F6BFF) 12%, transparent) 0%,
26
+ transparent 60%),
27
+ var(--color-bg, transparent);
28
+ }
29
+ .ras-loader-inner {
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ gap: 22px;
34
+ animation: ras-boot-in 0.5s cubic-bezier(0.22, 0.7, 0.24, 1) both;
35
+ }
36
+ .ras-loader-mark {
37
+ position: relative;
38
+ display: grid;
39
+ place-items: center;
40
+ width: 76px;
41
+ height: 76px;
42
+ }
43
+ /* Soft pulsing halo behind the logo. */
44
+ .ras-loader-mark::before {
45
+ content: "";
46
+ position: absolute;
47
+ inset: 0;
48
+ border-radius: 22px;
49
+ background: color-mix(in srgb, var(--color-accent, #4F6BFF) 45%, transparent);
50
+ filter: blur(16px);
51
+ animation: ras-boot-glow 1.8s var(--ras-ease, cubic-bezier(0.22, 0.7, 0.24, 1)) infinite;
52
+ }
53
+ /* Rotating conic ring that frames the mark. */
54
+ .ras-loader-mark::after {
55
+ content: "";
56
+ position: absolute;
57
+ inset: -7px;
58
+ border-radius: 26px;
59
+ padding: 2px;
60
+ background: conic-gradient(from 0deg,
61
+ transparent 0deg,
62
+ var(--color-accent, #4F6BFF) 90deg,
63
+ transparent 200deg);
64
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
65
+ -webkit-mask-composite: xor;
66
+ mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
67
+ mask-composite: exclude;
68
+ animation: ras-boot-spin 1.4s linear infinite;
69
+ }
70
+ .ras-loader-logo {
71
+ position: relative;
72
+ display: grid;
73
+ place-items: center;
74
+ width: 62px;
75
+ height: 62px;
76
+ border-radius: 18px;
77
+ background: var(--color-surface, var(--color-bg, #fff));
78
+ box-shadow:
79
+ 0 8px 24px color-mix(in srgb, var(--color-accent, #4F6BFF) 22%, transparent),
80
+ 0 1px 0 color-mix(in srgb, var(--color-text, #0B1020) 6%, transparent) inset;
81
+ animation: ras-boot-bob 3s ease-in-out infinite;
82
+ }
83
+ .ras-loader-title {
84
+ font-size: 17px;
85
+ font-weight: 650;
86
+ letter-spacing: -0.01em;
87
+ color: var(--color-text, #0B1020);
88
+ font-family: var(--font-family-sans, "Inter", system-ui, sans-serif);
89
+ }
90
+ .ras-loader-title .ras-loader-admin {
91
+ color: var(--color-accent, #4F6BFF);
92
+ font-weight: 600;
93
+ }
94
+ /* Indeterminate track. */
95
+ .ras-loader-track {
96
+ position: relative;
97
+ width: 172px;
98
+ height: 4px;
99
+ border-radius: 999px;
100
+ overflow: hidden;
101
+ background: color-mix(in srgb, var(--color-text, #0B1020) 10%, transparent);
102
+ }
103
+ .ras-loader-track::after {
104
+ content: "";
105
+ position: absolute;
106
+ top: 0;
107
+ left: 0;
108
+ height: 100%;
109
+ width: 40%;
110
+ border-radius: inherit;
111
+ background: linear-gradient(90deg,
112
+ transparent,
113
+ var(--color-accent, #4F6BFF),
114
+ color-mix(in srgb, var(--color-accent, #4F6BFF) 40%, transparent));
115
+ animation: ras-boot-slide 1.25s cubic-bezier(0.5, 0.1, 0.3, 1) infinite;
116
+ }
117
+ .ras-loader-label {
118
+ margin: -6px 0 0;
119
+ font-size: 13.5px;
120
+ color: var(--color-text-muted, #6b7590);
121
+ font-family: var(--font-family-sans, "Inter", system-ui, sans-serif);
122
+ text-align: center;
123
+ max-width: 320px;
124
+ }
125
+ .ras-loader.is-error .ras-loader-mark::before,
126
+ .ras-loader.is-error .ras-loader-mark::after { animation-play-state: paused; opacity: 0.35; }
127
+ .ras-loader.is-error .ras-loader-label { color: var(--color-danger, #d64550); }
128
+
129
+ @keyframes ras-boot-in {
130
+ from { opacity: 0; transform: translateY(8px) scale(0.985); }
131
+ to { opacity: 1; transform: none; }
132
+ }
133
+ @keyframes ras-boot-spin { to { transform: rotate(360deg); } }
134
+ @keyframes ras-boot-glow {
135
+ 0%, 100% { opacity: 0.45; transform: scale(0.9); }
136
+ 50% { opacity: 0.85; transform: scale(1.08); }
137
+ }
138
+ @keyframes ras-boot-bob {
139
+ 0%, 100% { transform: translateY(0); }
140
+ 50% { transform: translateY(-3px); }
141
+ }
142
+ @keyframes ras-boot-slide {
143
+ 0% { left: -42%; }
144
+ 100% { left: 100%; }
145
+ }
146
+ @media (prefers-reduced-motion: reduce) {
147
+ .ras-loader-inner,
148
+ .ras-loader-mark::before,
149
+ .ras-loader-mark::after,
150
+ .ras-loader-logo,
151
+ .ras-loader-track::after { animation: none; }
152
+ .ras-loader-track::after { left: 0; width: 100%; opacity: 0.7; }
153
+ }
154
+ `;
155
+
156
+ /** Inject the scoped stylesheet exactly once. */
157
+ function useBootStyles(): void {
158
+ if (typeof document === "undefined") return;
159
+ if (document.getElementById(STYLE_ID)) return;
160
+ const el = document.createElement("style");
161
+ el.id = STYLE_ID;
162
+ el.textContent = BOOT_CSS;
163
+ document.head.appendChild(el);
164
+ }
165
+
166
+ export interface BootScreenProps {
167
+ /** The status line shown beneath the mark. */
168
+ label: string;
169
+ /** Render in the error palette (paused halo, danger-coloured label). */
170
+ error?: boolean;
171
+ }
172
+
173
+ /**
174
+ * A branded, animated boot screen — the product logo in a floating tile with a
175
+ * pulsing halo and a rotating ring, the wordmark, an indeterminate progress
176
+ * track, and the current status. Matches the polish of the rest of the admin.
177
+ */
178
+ export function BootScreen({ label, error }: BootScreenProps): React.JSX.Element {
179
+ useBootStyles();
180
+ return (
181
+ <div
182
+ className={`ras-loader${error ? " is-error" : ""}`}
183
+ role="status"
184
+ aria-live="polite"
185
+ aria-busy={!error}
186
+ >
187
+ <div className="ras-loader-inner">
188
+ <div className="ras-loader-mark">
189
+ <div className="ras-loader-logo">
190
+ <BrandMark size={34} />
191
+ </div>
192
+ </div>
193
+ <div className="ras-loader-title">
194
+ React API Stack <span className="ras-loader-admin">Admin</span>
195
+ </div>
196
+ {!error ? <div className="ras-loader-track" aria-hidden="true" /> : null}
197
+ <p className="ras-loader-label">{label}</p>
198
+ </div>
199
+ </div>
200
+ );
201
+ }