create-volt 0.29.0 → 0.30.0
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
|
@@ -4,6 +4,20 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.30.0] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Setup wizard: typing in a settings field no longer drops focus.** The
|
|
11
|
+
conditional sections (DB-driver fields, S3 fields) read the whole `state()`
|
|
12
|
+
signal, so every keystroke re-rendered the section and recreated the `<input>`,
|
|
13
|
+
losing focus. They now key on memoized primitive derivations (`dbDriver`,
|
|
14
|
+
`mediaDriver`, `hasDb`/`hasMailer`/`hasMedia`) so a section re-renders only when
|
|
15
|
+
its discriminant changes.
|
|
16
|
+
- **`volt.js`: reactive `value` bindings skip redundant DOM writes.** `setAttr`
|
|
17
|
+
no longer reassigns `node.value` when it already equals the new value — those
|
|
18
|
+
writes were resetting the caret to the end while typing. Benefits every Volt
|
|
19
|
+
app, not just the wizard.
|
|
20
|
+
|
|
7
21
|
## [0.29.0] - 2026-06-29
|
|
8
22
|
|
|
9
23
|
### Fixed
|
|
@@ -399,6 +413,7 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
399
413
|
watching and full-page hot reload. Supports `--skip-install` and `--force`,
|
|
400
414
|
and auto-detects npm / pnpm / yarn / bun for the install step.
|
|
401
415
|
|
|
416
|
+
[0.30.0]: https://github.com/MIR-2025/volt/releases/tag/v0.30.0
|
|
402
417
|
[0.29.0]: https://github.com/MIR-2025/volt/releases/tag/v0.29.0
|
|
403
418
|
[0.28.0]: https://github.com/MIR-2025/volt/releases/tag/v0.28.0
|
|
404
419
|
[0.27.0]: https://github.com/MIR-2025/volt/releases/tag/v0.27.0
|
package/package.json
CHANGED
|
@@ -127,7 +127,8 @@ export function mount(target, ...children) {
|
|
|
127
127
|
|
|
128
128
|
function setAttr(node, name, value) {
|
|
129
129
|
if (name === "value") {
|
|
130
|
-
|
|
130
|
+
const v = value ?? "";
|
|
131
|
+
if (node.value !== v) node.value = v; // skip redundant writes — they reset the caret while typing
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
if (name === "checked" || name === "disabled" || name === "selected") {
|
|
@@ -75,6 +75,14 @@ function genEnv(s) {
|
|
|
75
75
|
}
|
|
76
76
|
const env = computed(() => genEnv(state()));
|
|
77
77
|
const eff = computed(() => effective(state()));
|
|
78
|
+
// Memoized, primitive-valued derivations: a conditional section keyed on these
|
|
79
|
+
// only re-renders when the *discriminant* changes — not on every keystroke in a
|
|
80
|
+
// field it contains (which would recreate the input and drop focus).
|
|
81
|
+
const dbDriver = computed(() => state().dbDriver);
|
|
82
|
+
const mediaDriver = computed(() => state().mediaDriver);
|
|
83
|
+
const hasDb = computed(() => eff().includes("db"));
|
|
84
|
+
const hasMailer = computed(() => eff().includes("mailer"));
|
|
85
|
+
const hasMedia = computed(() => eff().includes("media"));
|
|
78
86
|
|
|
79
87
|
async function testDb() {
|
|
80
88
|
const s = state();
|
|
@@ -137,7 +145,7 @@ const addonRow = (a) =>
|
|
|
137
145
|
const dbSettings = () =>
|
|
138
146
|
html`<div class="mb-2">
|
|
139
147
|
<label class="form-label small mb-1">Database (DB_DRIVER)</label>
|
|
140
|
-
<select class="form-select" value=${() =>
|
|
148
|
+
<select class="form-select" value=${() => dbDriver()} onchange=${(e) => set({ dbDriver: e.target.value })}>
|
|
141
149
|
<option value="memory">memory (no setup)</option>
|
|
142
150
|
<option value="mongodb">mongodb</option>
|
|
143
151
|
<option value="mysql">mysql</option>
|
|
@@ -145,23 +153,23 @@ const dbSettings = () =>
|
|
|
145
153
|
</select>
|
|
146
154
|
</div>
|
|
147
155
|
${() =>
|
|
148
|
-
|
|
156
|
+
dbDriver() === "mongodb"
|
|
149
157
|
? html`${field("MONGODB_URI", "mongoUri", "mongodb://user:pass@host:27017/db")}${field("MONGODB_DATABASE", "mongoDb", "db")}`
|
|
150
|
-
:
|
|
151
|
-
? field("DATABASE_URL", "dbUrl",
|
|
158
|
+
: dbDriver() === "mysql" || dbDriver() === "postgres"
|
|
159
|
+
? field("DATABASE_URL", "dbUrl", dbDriver() + "://user:pass@host/db")
|
|
152
160
|
: null}
|
|
153
|
-
${() => (
|
|
161
|
+
${() => (dbDriver() !== "memory" ? html`<button class="btn btn-sm btn-outline-secondary mb-2" onclick=${testDb}>Test connection</button>` : null)}`;
|
|
154
162
|
|
|
155
163
|
const mediaSettings = () =>
|
|
156
164
|
html`<div class="mb-2">
|
|
157
165
|
<label class="form-label small mb-1">Media storage (MEDIA_DRIVER)</label>
|
|
158
|
-
<select class="form-select" value=${() =>
|
|
166
|
+
<select class="form-select" value=${() => mediaDriver()} onchange=${(e) => set({ mediaDriver: e.target.value })}>
|
|
159
167
|
<option value="local">local (disk)</option>
|
|
160
168
|
<option value="s3">s3 — AWS S3 / DigitalOcean Spaces</option>
|
|
161
169
|
</select>
|
|
162
170
|
</div>
|
|
163
171
|
${() =>
|
|
164
|
-
|
|
172
|
+
mediaDriver() === "s3"
|
|
165
173
|
? html`${field("S3_ENDPOINT", "s3Endpoint", "https://nyc3.digitaloceanspaces.com")}${field("S3_REGION", "s3Region", "us-east-1")}${field("S3_BUCKET", "s3Bucket", "my-space")}${field("S3_KEY", "s3Key", "access key")}${field("S3_SECRET", "s3Secret", "secret key")}${field("S3_PUBLIC_BASE (optional CDN base)", "s3PublicBase", "https://cdn.example.com")}`
|
|
166
174
|
: null}`;
|
|
167
175
|
|
|
@@ -177,9 +185,9 @@ mount(
|
|
|
177
185
|
html`<div class="card-x p-4 mb-3">
|
|
178
186
|
<h2 class="h6 mb-3">Settings</h2>
|
|
179
187
|
${field("PORT", "port", String(defaultPort))}
|
|
180
|
-
${() => (
|
|
181
|
-
${() => (
|
|
182
|
-
${() => (
|
|
188
|
+
${() => (hasDb() ? dbSettings() : null)}
|
|
189
|
+
${() => (hasMailer() ? html`${field("SMTP_URL (optional)", "smtpUrl", "smtp://user:pass@smtp.host:587")}${field("MAIL_FROM", "mailFrom", "App <no-reply@you.com>")}` : null)}
|
|
190
|
+
${() => (hasMedia() ? mediaSettings() : null)}
|
|
183
191
|
</div>`,
|
|
184
192
|
html`<div class="card-x p-4 mb-3">
|
|
185
193
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
@@ -127,7 +127,8 @@ export function mount(target, ...children) {
|
|
|
127
127
|
|
|
128
128
|
function setAttr(node, name, value) {
|
|
129
129
|
if (name === "value") {
|
|
130
|
-
|
|
130
|
+
const v = value ?? "";
|
|
131
|
+
if (node.value !== v) node.value = v; // skip redundant writes — they reset the caret while typing
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
if (name === "checked" || name === "disabled" || name === "selected") {
|
|
@@ -127,7 +127,8 @@ export function mount(target, ...children) {
|
|
|
127
127
|
|
|
128
128
|
function setAttr(node, name, value) {
|
|
129
129
|
if (name === "value") {
|
|
130
|
-
|
|
130
|
+
const v = value ?? "";
|
|
131
|
+
if (node.value !== v) node.value = v; // skip redundant writes — they reset the caret while typing
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
if (name === "checked" || name === "disabled" || name === "selected") {
|
|
@@ -75,6 +75,14 @@ function genEnv(s) {
|
|
|
75
75
|
}
|
|
76
76
|
const env = computed(() => genEnv(state()));
|
|
77
77
|
const eff = computed(() => effective(state()));
|
|
78
|
+
// Memoized, primitive-valued derivations: a conditional section keyed on these
|
|
79
|
+
// only re-renders when the *discriminant* changes — not on every keystroke in a
|
|
80
|
+
// field it contains (which would recreate the input and drop focus).
|
|
81
|
+
const dbDriver = computed(() => state().dbDriver);
|
|
82
|
+
const mediaDriver = computed(() => state().mediaDriver);
|
|
83
|
+
const hasDb = computed(() => eff().includes("db"));
|
|
84
|
+
const hasMailer = computed(() => eff().includes("mailer"));
|
|
85
|
+
const hasMedia = computed(() => eff().includes("media"));
|
|
78
86
|
|
|
79
87
|
async function testDb() {
|
|
80
88
|
const s = state();
|
|
@@ -137,7 +145,7 @@ const addonRow = (a) =>
|
|
|
137
145
|
const dbSettings = () =>
|
|
138
146
|
html`<div class="mb-2">
|
|
139
147
|
<label class="form-label small mb-1">Database (DB_DRIVER)</label>
|
|
140
|
-
<select class="form-select" value=${() =>
|
|
148
|
+
<select class="form-select" value=${() => dbDriver()} onchange=${(e) => set({ dbDriver: e.target.value })}>
|
|
141
149
|
<option value="memory">memory (no setup)</option>
|
|
142
150
|
<option value="mongodb">mongodb</option>
|
|
143
151
|
<option value="mysql">mysql</option>
|
|
@@ -145,23 +153,23 @@ const dbSettings = () =>
|
|
|
145
153
|
</select>
|
|
146
154
|
</div>
|
|
147
155
|
${() =>
|
|
148
|
-
|
|
156
|
+
dbDriver() === "mongodb"
|
|
149
157
|
? html`${field("MONGODB_URI", "mongoUri", "mongodb://user:pass@host:27017/db")}${field("MONGODB_DATABASE", "mongoDb", "db")}`
|
|
150
|
-
:
|
|
151
|
-
? field("DATABASE_URL", "dbUrl",
|
|
158
|
+
: dbDriver() === "mysql" || dbDriver() === "postgres"
|
|
159
|
+
? field("DATABASE_URL", "dbUrl", dbDriver() + "://user:pass@host/db")
|
|
152
160
|
: null}
|
|
153
|
-
${() => (
|
|
161
|
+
${() => (dbDriver() !== "memory" ? html`<button class="btn btn-sm btn-outline-secondary mb-2" onclick=${testDb}>Test connection</button>` : null)}`;
|
|
154
162
|
|
|
155
163
|
const mediaSettings = () =>
|
|
156
164
|
html`<div class="mb-2">
|
|
157
165
|
<label class="form-label small mb-1">Media storage (MEDIA_DRIVER)</label>
|
|
158
|
-
<select class="form-select" value=${() =>
|
|
166
|
+
<select class="form-select" value=${() => mediaDriver()} onchange=${(e) => set({ mediaDriver: e.target.value })}>
|
|
159
167
|
<option value="local">local (disk)</option>
|
|
160
168
|
<option value="s3">s3 — AWS S3 / DigitalOcean Spaces</option>
|
|
161
169
|
</select>
|
|
162
170
|
</div>
|
|
163
171
|
${() =>
|
|
164
|
-
|
|
172
|
+
mediaDriver() === "s3"
|
|
165
173
|
? html`${field("S3_ENDPOINT", "s3Endpoint", "https://nyc3.digitaloceanspaces.com")}${field("S3_REGION", "s3Region", "us-east-1")}${field("S3_BUCKET", "s3Bucket", "my-space")}${field("S3_KEY", "s3Key", "access key")}${field("S3_SECRET", "s3Secret", "secret key")}${field("S3_PUBLIC_BASE (optional CDN base)", "s3PublicBase", "https://cdn.example.com")}`
|
|
166
174
|
: null}`;
|
|
167
175
|
|
|
@@ -177,9 +185,9 @@ mount(
|
|
|
177
185
|
html`<div class="card-x p-4 mb-3">
|
|
178
186
|
<h2 class="h6 mb-3">Settings</h2>
|
|
179
187
|
${field("PORT", "port", String(defaultPort))}
|
|
180
|
-
${() => (
|
|
181
|
-
${() => (
|
|
182
|
-
${() => (
|
|
188
|
+
${() => (hasDb() ? dbSettings() : null)}
|
|
189
|
+
${() => (hasMailer() ? html`${field("SMTP_URL (optional)", "smtpUrl", "smtp://user:pass@smtp.host:587")}${field("MAIL_FROM", "mailFrom", "App <no-reply@you.com>")}` : null)}
|
|
190
|
+
${() => (hasMedia() ? mediaSettings() : null)}
|
|
183
191
|
</div>`,
|
|
184
192
|
html`<div class="card-x p-4 mb-3">
|
|
185
193
|
<div class="d-flex justify-content-between align-items-center mb-2">
|