free-coding-models 0.5.32 → 0.5.35
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/LICENSE +43 -21
- package/README.md +2 -1
- package/changelog/v0.5.33.md +4 -0
- package/changelog/v0.5.34.md +4 -0
- package/changelog/v0.5.35.md +4 -0
- package/package.json +3 -2
- package/sources.js +12 -52
- package/src/core/router-daemon.js +56 -49
- package/src/core/schema-normalizer.js +172 -0
- package/src/core/tool-bootstrap.js +11 -0
- package/src/core/tool-launchers.js +37 -2
- package/src/core/tool-metadata.js +3 -0
- package/web/dist/assets/index-B5tTa7_O.js +40 -0
- package/web/dist/assets/index-r8niexgJ.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/src/App.jsx +93 -105
- package/web/src/components/changelog/ChangelogView.module.css +13 -14
- package/web/src/components/dashboard/FilterBar.jsx +17 -2
- package/web/src/components/dashboard/FilterBar.module.css +68 -21
- package/web/src/components/dashboard/ModelTable.jsx +118 -42
- package/web/src/components/dashboard/ModelTable.module.css +82 -27
- package/web/src/components/help/HelpView.module.css +13 -14
- package/web/src/components/install/InstallEndpointsView.module.css +13 -13
- package/web/src/components/installed/InstalledModelsView.module.css +13 -13
- package/web/src/components/layout/Header.jsx +28 -28
- package/web/src/components/layout/Header.module.css +56 -30
- package/web/src/components/palette/CommandPalette.jsx +14 -14
- package/web/src/components/playground/PlaygroundView.module.css +13 -14
- package/web/src/components/recommend/RecommendView.module.css +13 -11
- package/web/src/components/router/RouterView.module.css +13 -13
- package/web/src/components/tools/ToolPicker.module.css +27 -14
- package/web/src/global.css +58 -31
- package/web/src/hooks/urlState.constants.js +1 -1
- package/web/src/hooks/useUrlState.js +19 -3
- package/web/dist/assets/index-C_tCF0A5.js +0 -40
- package/web/dist/assets/index-CsFt3qt5.css +0 -1
|
@@ -37,14 +37,14 @@ const PING_MODE_CYCLE = ['speed', 'normal', 'slow', 'forced']
|
|
|
37
37
|
// 📖 has a few commands the TUI doesn't). They are appended to the TUI registry
|
|
38
38
|
// 📖 after a `Web` separator so the two surfaces stay easy to compare.
|
|
39
39
|
function buildWebEntries(deps) {
|
|
40
|
-
const { onCycleTheme, onResetView, onSetPingMode,
|
|
40
|
+
const { onCycleTheme, onResetView, onSetPingMode, theme, pingMode, modelsCount, onExport, onNavigate } = deps
|
|
41
41
|
const web = [
|
|
42
|
-
// Pages
|
|
43
|
-
{ id: 'page.help', section: 'page', label: 'Open Help', keywords: ['help', 'shortcuts', 'reference'], run: () =>
|
|
44
|
-
{ id: 'page.changelog', section: 'page', label: 'Open Changelog', keywords: ['changelog', 'release', 'history', 'version'], run: () =>
|
|
45
|
-
{ id: 'page.playground', section: 'page', label: 'Open Playground', keywords: ['playground', 'chat', 'try', 'prompt', 'router'], run: () =>
|
|
46
|
-
{ id: 'page.install-endpoints', section: 'page', label: 'Open Install Endpoints
|
|
47
|
-
{ id: 'page.installed-models', section: 'page', label: 'Open Installed Models
|
|
42
|
+
// Pages
|
|
43
|
+
{ id: 'page.help', section: 'page', label: 'Open Help', keywords: ['help', 'shortcuts', 'reference'], run: () => onNavigate?.('help') },
|
|
44
|
+
{ id: 'page.changelog', section: 'page', label: 'Open Changelog', keywords: ['changelog', 'release', 'history', 'version'], run: () => onNavigate?.('changelog') },
|
|
45
|
+
{ id: 'page.playground', section: 'page', label: 'Open Playground', keywords: ['playground', 'chat', 'try', 'prompt', 'router'], run: () => onNavigate?.('playground') },
|
|
46
|
+
{ id: 'page.install-endpoints', section: 'page', label: 'Open Install Endpoints', keywords: ['install', 'endpoint', 'tool', 'configure'], run: () => onNavigate?.('install-endpoints') },
|
|
47
|
+
{ id: 'page.installed-models', section: 'page', label: 'Open Installed Models', keywords: ['installed', 'models', 'tools'], run: () => onNavigate?.('installed-models') },
|
|
48
48
|
|
|
49
49
|
// Theme
|
|
50
50
|
{ id: 'action.theme.cycle', section: 'action', label: `Cycle theme (current: ${theme})`, keywords: ['theme', 'dark', 'light', 'auto'], run: onCycleTheme },
|
|
@@ -92,7 +92,7 @@ export default function CommandPalette({
|
|
|
92
92
|
})
|
|
93
93
|
const webEntries = buildWebEntries({
|
|
94
94
|
onCycleTheme, onResetView, onSetPingMode,
|
|
95
|
-
|
|
95
|
+
theme, pingMode, modelsCount: models?.length ?? 0, onExport, onNavigate,
|
|
96
96
|
})
|
|
97
97
|
// 📖 Update banner entry (only when an update is available) — mirrors
|
|
98
98
|
// 📖 the TUI palette's "auto-prepended when newer version known" rule.
|
|
@@ -177,12 +177,12 @@ export default function CommandPalette({
|
|
|
177
177
|
// 📖 ships in M3), we route the user to the right header menu.
|
|
178
178
|
const id = item.id
|
|
179
179
|
if (id === 'open-settings') { onNavigate?.('settings'); onClose(); return }
|
|
180
|
-
if (id === 'open-help') {
|
|
181
|
-
if (id === 'open-changelog') {
|
|
182
|
-
if (id === 'open-recommend') {
|
|
183
|
-
if (id === 'open-router-dashboard') {
|
|
184
|
-
if (id === 'open-installed-models') {
|
|
185
|
-
if (id === 'open-install-endpoints') {
|
|
180
|
+
if (id === 'open-help') { onNavigate?.('help'); onClose(); return }
|
|
181
|
+
if (id === 'open-changelog') { onNavigate?.('changelog'); onClose(); return }
|
|
182
|
+
if (id === 'open-recommend') { onNavigate?.('recommend'); onClose(); return }
|
|
183
|
+
if (id === 'open-router-dashboard') { onNavigate?.('router'); onClose(); return }
|
|
184
|
+
if (id === 'open-installed-models') { onNavigate?.('installed-models'); onClose(); return }
|
|
185
|
+
if (id === 'open-install-endpoints') { onNavigate?.('install-endpoints'); onClose(); return }
|
|
186
186
|
if (id === 'action-update-now') { onRunUpdate?.(); onClose(); return }
|
|
187
187
|
if (id === 'action-cycle-theme') { onCycleTheme?.(); onClose(); return }
|
|
188
188
|
if (id === 'action-reset-view') { onResetView?.(); onClose(); return }
|
|
@@ -5,27 +5,26 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
.overlay {
|
|
8
|
-
position: fixed;
|
|
9
|
-
inset: 0;
|
|
10
|
-
z-index: 200;
|
|
11
|
-
background: rgba(0, 0, 0, 0.6);
|
|
12
8
|
display: flex;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
flex: 1;
|
|
11
|
+
min-height: 0;
|
|
12
|
+
width: 100%;
|
|
13
|
+
padding: 24px;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
.modal {
|
|
19
|
-
background: var(--bg-
|
|
20
|
-
border: 1px solid var(--border, #
|
|
17
|
+
background: var(--color-bg-card, #080908);
|
|
18
|
+
border: 1px solid var(--color-border, #161a16);
|
|
21
19
|
border-radius: 12px;
|
|
22
|
-
width:
|
|
23
|
-
max-width:
|
|
24
|
-
|
|
20
|
+
width: 100%;
|
|
21
|
+
max-width: 1000px;
|
|
22
|
+
margin: 0 auto;
|
|
23
|
+
flex: 1;
|
|
25
24
|
display: flex;
|
|
26
25
|
flex-direction: column;
|
|
27
|
-
box-shadow: 0 20px
|
|
28
|
-
|
|
26
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
27
|
+
min-height: 0;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
@keyframes fadeIn {
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
/** @file web/src/components/recommend/RecommendView.module.css */
|
|
2
2
|
.backdrop {
|
|
3
|
-
position: fixed;
|
|
4
|
-
inset: 0;
|
|
5
|
-
z-index: 480;
|
|
6
3
|
display: flex;
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
flex: 1;
|
|
6
|
+
min-height: 0;
|
|
7
|
+
width: 100%;
|
|
9
8
|
padding: 24px;
|
|
10
|
-
background: rgba(0, 0, 0, .64);
|
|
11
|
-
backdrop-filter: blur(4px);
|
|
12
9
|
}
|
|
13
10
|
.modal {
|
|
14
|
-
width:
|
|
15
|
-
max-
|
|
11
|
+
width: 100%;
|
|
12
|
+
max-width: 980px;
|
|
13
|
+
margin: 0 auto;
|
|
16
14
|
overflow: auto;
|
|
17
15
|
border: 1px solid var(--color-border);
|
|
18
16
|
border-radius: 16px;
|
|
19
|
-
background: var(--color-bg-
|
|
17
|
+
background: var(--color-bg-card, #080908);
|
|
20
18
|
color: var(--color-text);
|
|
21
|
-
box-shadow: 0 20px
|
|
19
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
20
|
+
flex: 1;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
min-height: 0;
|
|
22
24
|
}
|
|
23
25
|
.header,
|
|
24
26
|
.footer { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 20px 24px; border-bottom: 1px solid var(--color-border); }
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
.overlay {
|
|
2
|
-
position: fixed;
|
|
3
|
-
inset: 0;
|
|
4
|
-
z-index: 200;
|
|
5
|
-
background: rgba(0, 0, 0, 0.6);
|
|
6
2
|
display: flex;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
flex: 1;
|
|
5
|
+
min-height: 0;
|
|
6
|
+
width: 100%;
|
|
7
|
+
padding: 24px;
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
.modal {
|
|
13
|
-
background: var(--bg-
|
|
14
|
-
border: 1px solid var(--border, #
|
|
11
|
+
background: var(--color-bg-card, #080908);
|
|
12
|
+
border: 1px solid var(--color-border, #161a16);
|
|
15
13
|
border-radius: 12px;
|
|
16
|
-
width:
|
|
17
|
-
max-width:
|
|
18
|
-
|
|
14
|
+
width: 100%;
|
|
15
|
+
max-width: 1000px;
|
|
16
|
+
margin: 0 auto;
|
|
17
|
+
flex: 1;
|
|
19
18
|
display: flex;
|
|
20
19
|
flex-direction: column;
|
|
21
|
-
box-shadow: 0
|
|
20
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
21
|
+
min-height: 0;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
.header {
|
|
@@ -8,33 +8,46 @@
|
|
|
8
8
|
align-items: center;
|
|
9
9
|
gap: 4px;
|
|
10
10
|
}
|
|
11
|
-
.trigger
|
|
12
|
-
.cycle {
|
|
11
|
+
.trigger {
|
|
13
12
|
height: 30px;
|
|
14
13
|
display: inline-flex;
|
|
15
14
|
align-items: center;
|
|
16
15
|
justify-content: center;
|
|
17
|
-
border: 1px solid var(--color-border);
|
|
18
|
-
background: var(--color-surface);
|
|
19
|
-
color: var(--color-text);
|
|
20
|
-
cursor: pointer;
|
|
21
|
-
transition: background 150ms, border-color 150ms, color 150ms;
|
|
22
|
-
}
|
|
23
|
-
.trigger {
|
|
24
16
|
gap: 6px;
|
|
25
|
-
padding: 0
|
|
17
|
+
padding: 0 12px;
|
|
26
18
|
border-radius: 6px;
|
|
27
|
-
font-size:
|
|
28
|
-
font-weight:
|
|
19
|
+
font-size: 11px;
|
|
20
|
+
font-weight: 800;
|
|
21
|
+
font-family: var(--font-mono);
|
|
22
|
+
text-transform: uppercase;
|
|
23
|
+
letter-spacing: 0.5px;
|
|
24
|
+
background: var(--color-accent);
|
|
25
|
+
color: var(--color-bg);
|
|
26
|
+
border: 1px solid var(--color-accent);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition: all 150ms var(--ease-out);
|
|
29
29
|
}
|
|
30
30
|
.cycle {
|
|
31
|
+
height: 30px;
|
|
31
32
|
width: 30px;
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
border: 1px solid var(--color-border);
|
|
37
|
+
background: var(--color-surface);
|
|
38
|
+
color: var(--color-text);
|
|
32
39
|
border-radius: 6px;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
transition: all 150ms var(--ease-out);
|
|
42
|
+
}
|
|
43
|
+
.trigger:hover {
|
|
44
|
+
background: var(--color-accent-hover);
|
|
45
|
+
border-color: var(--color-accent-hover);
|
|
46
|
+
color: var(--color-bg);
|
|
33
47
|
}
|
|
34
|
-
.trigger:hover,
|
|
35
48
|
.cycle:hover {
|
|
36
49
|
background: var(--color-bg-hover);
|
|
37
|
-
border-color: var(--color-
|
|
50
|
+
border-color: var(--color-border-hover);
|
|
38
51
|
color: var(--color-accent);
|
|
39
52
|
}
|
|
40
53
|
.emoji { font-size: 14px; line-height: 1; }
|
package/web/src/global.css
CHANGED
|
@@ -8,24 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
/* ─── CSS Custom Properties (Design Tokens) ─── */
|
|
10
10
|
:root {
|
|
11
|
-
--color-brand: #
|
|
12
|
-
--color-bg: #
|
|
13
|
-
--color-bg-elevated: #
|
|
14
|
-
--color-bg-card: #
|
|
15
|
-
--color-bg-hover: #
|
|
16
|
-
--color-bg-active: #
|
|
17
|
-
--color-surface: #
|
|
18
|
-
--color-border: #
|
|
19
|
-
--color-border-hover: #
|
|
11
|
+
--color-brand: #c0f20c;
|
|
12
|
+
--color-bg: #050505;
|
|
13
|
+
--color-bg-elevated: #090a09;
|
|
14
|
+
--color-bg-card: #080908;
|
|
15
|
+
--color-bg-hover: #121512;
|
|
16
|
+
--color-bg-active: #181d18;
|
|
17
|
+
--color-surface: #0d100d;
|
|
18
|
+
--color-border: #161a16;
|
|
19
|
+
--color-border-hover: #2e3b2e;
|
|
20
20
|
|
|
21
21
|
--color-text: #ffffff;
|
|
22
|
-
--color-text-muted: #
|
|
23
|
-
--color-text-dim: #
|
|
22
|
+
--color-text-muted: #8fa08f;
|
|
23
|
+
--color-text-dim: #455245;
|
|
24
24
|
|
|
25
|
-
--color-accent: #
|
|
26
|
-
--color-accent-hover: #
|
|
27
|
-
--color-accent-glow: rgba(
|
|
28
|
-
--color-accent-dim: rgba(
|
|
25
|
+
--color-accent: #c0f20c;
|
|
26
|
+
--color-accent-hover: #d2ff2b;
|
|
27
|
+
--color-accent-glow: rgba(192, 242, 12, 0.15);
|
|
28
|
+
--color-accent-dim: rgba(192, 242, 12, 0.08);
|
|
29
29
|
|
|
30
30
|
--color-danger: #ff4444;
|
|
31
31
|
--color-danger-dim: rgba(255, 68, 68, 0.1);
|
|
@@ -160,7 +160,7 @@ textarea:focus-visible {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
html {
|
|
163
|
-
font-size:
|
|
163
|
+
font-size: 16px;
|
|
164
164
|
scroll-behavior: smooth;
|
|
165
165
|
-webkit-font-smoothing: antialiased;
|
|
166
166
|
-moz-osx-font-smoothing: grayscale;
|
|
@@ -179,23 +179,16 @@ body {
|
|
|
179
179
|
|
|
180
180
|
a { color: var(--color-accent); text-decoration: none; }
|
|
181
181
|
a:hover { color: var(--color-accent-hover); }
|
|
182
|
-
code { font-family: var(--font-mono); font-size:
|
|
182
|
+
code { font-family: var(--font-mono); font-size: 14px; background: var(--color-surface); padding: 2px 6px; border-radius: 4px; }
|
|
183
183
|
|
|
184
184
|
/* ─── Background Effects ─── */
|
|
185
|
-
.
|
|
186
|
-
display: none;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.bg-glow {
|
|
190
|
-
display: none;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
[data-theme="light"] .bg-glow { opacity: 0.12; }
|
|
195
|
-
[data-theme="light"] .bg-grid {
|
|
185
|
+
[data-theme="light"] .app-shell {
|
|
196
186
|
background-image:
|
|
197
|
-
linear-gradient(rgba(0,0,0,0.
|
|
198
|
-
linear-gradient(90deg, rgba(0,0,0,0.
|
|
187
|
+
linear-gradient(rgba(0,0,0,0.03) 1px, transparent 1px),
|
|
188
|
+
linear-gradient(90deg, rgba(0,0,0,0.03) 1px, transparent 1px);
|
|
189
|
+
}
|
|
190
|
+
[data-theme="light"] .app-shell::before {
|
|
191
|
+
background: radial-gradient(circle, rgba(0, 143, 77, 0.03) 0%, transparent 70%);
|
|
199
192
|
}
|
|
200
193
|
|
|
201
194
|
/* ─── App Layout ─── */
|
|
@@ -207,6 +200,25 @@ code { font-family: var(--font-mono); font-size: 12px; background: var(--color-s
|
|
|
207
200
|
width: 100%;
|
|
208
201
|
position: relative;
|
|
209
202
|
z-index: 1;
|
|
203
|
+
background-color: var(--color-bg);
|
|
204
|
+
/* background grid effect */
|
|
205
|
+
background-image:
|
|
206
|
+
linear-gradient(rgba(192, 242, 12, 0.012) 1px, transparent 1px),
|
|
207
|
+
linear-gradient(90deg, rgba(192, 242, 12, 0.012) 1px, transparent 1px);
|
|
208
|
+
background-size: 50px 50px;
|
|
209
|
+
background-position: center top;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.app-shell::before {
|
|
213
|
+
content: "";
|
|
214
|
+
position: absolute;
|
|
215
|
+
top: 0;
|
|
216
|
+
left: 25%;
|
|
217
|
+
width: 600px;
|
|
218
|
+
height: 600px;
|
|
219
|
+
background: radial-gradient(circle, rgba(192, 242, 12, 0.03) 0%, transparent 70%);
|
|
220
|
+
z-index: -1;
|
|
221
|
+
pointer-events: none;
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
.app-content {
|
|
@@ -225,6 +237,21 @@ code { font-family: var(--font-mono); font-size: 12px; background: var(--color-s
|
|
|
225
237
|
min-height: 0;
|
|
226
238
|
}
|
|
227
239
|
|
|
240
|
+
/* 📖 Dashboard-only: bound the main box to the viewport height (minus the
|
|
241
|
+
📖 global header) so the model table scrolls INTERNALLY inside .scrollInner
|
|
242
|
+
📖 instead of growing the whole page. That internal scroll is what lets the
|
|
243
|
+
📖 green <thead> header be position:sticky and stay pinned while you scroll
|
|
244
|
+
📖 190+ model rows. Scoped to the dashboard only (main.dashboardView has
|
|
245
|
+
📖 higher specificity than the shared .view rule, so flex:none + the capped
|
|
246
|
+
📖 height reliably win). Settings / Analytics / Playground keep their layout. */
|
|
247
|
+
main.dashboardView {
|
|
248
|
+
flex: none;
|
|
249
|
+
height: calc(100vh - var(--header-h));
|
|
250
|
+
height: calc(100dvh - var(--header-h));
|
|
251
|
+
min-height: 0;
|
|
252
|
+
overflow: hidden;
|
|
253
|
+
}
|
|
254
|
+
|
|
228
255
|
.update-warning-banner {
|
|
229
256
|
position: sticky;
|
|
230
257
|
top: var(--header-h, 60px);
|
|
@@ -234,7 +261,7 @@ code { font-family: var(--font-mono); font-size: 12px; background: var(--color-s
|
|
|
234
261
|
color: #fff;
|
|
235
262
|
border-bottom: 1px solid rgba(255, 255, 255, 0.22);
|
|
236
263
|
font-weight: 800;
|
|
237
|
-
font-size:
|
|
264
|
+
font-size: 15px;
|
|
238
265
|
letter-spacing: 0.01em;
|
|
239
266
|
text-align: center;
|
|
240
267
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
|
|
@@ -14,7 +14,7 @@ const SORT_VALUES = new Set([
|
|
|
14
14
|
'latestPing', 'avg', 'condition', 'verdict', 'stability', 'uptime',
|
|
15
15
|
'aiLatency', 'tps', 'trend',
|
|
16
16
|
])
|
|
17
|
-
const VIEW_VALUES = new Set(['dashboard', 'settings', 'analytics', 'recommend', 'router', 'help', 'changelog'])
|
|
17
|
+
const VIEW_VALUES = new Set(['dashboard', 'settings', 'analytics', 'recommend', 'router', 'help', 'changelog', 'playground', 'install-endpoints', 'installed-models'])
|
|
18
18
|
const DIR_VALUES = new Set(['asc', 'desc'])
|
|
19
19
|
const TOOL_MODE_VALUES = new Set(TOOL_MODE_ORDER)
|
|
20
20
|
|
|
@@ -31,7 +31,17 @@ function parseUrlParams() {
|
|
|
31
31
|
if (typeof window === 'undefined') return null
|
|
32
32
|
const params = new URLSearchParams(window.location.search)
|
|
33
33
|
const out = {}
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
// 📖 Read pathname as the view slug
|
|
36
|
+
const pathname = window.location.pathname.replace(/^\/|\/$/g, '')
|
|
37
|
+
if (pathname && VALID_VIEWS.has(pathname)) {
|
|
38
|
+
out.view = pathname
|
|
39
|
+
} else if (params.has('view') && VALID_VIEWS.has(params.get('view'))) {
|
|
40
|
+
out.view = params.get('view')
|
|
41
|
+
} else {
|
|
42
|
+
out.view = 'dashboard'
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
if (params.has('tier') && VALID_TIERS.has(params.get('tier'))) out.tier = params.get('tier')
|
|
36
46
|
if (params.has('status') && VALID_STATUS.has(params.get('status'))) out.status = params.get('status')
|
|
37
47
|
if (params.has('provider')) out.provider = params.get('provider')
|
|
@@ -71,10 +81,16 @@ export function buildUrlParams(state) {
|
|
|
71
81
|
function writeUrl(state) {
|
|
72
82
|
if (typeof window === 'undefined') return
|
|
73
83
|
const params = buildUrlParams(state)
|
|
84
|
+
|
|
85
|
+
// 📖 Extract view from the search params so it becomes the pathname slug instead of a search param!
|
|
86
|
+
const view = params.get('view') || 'dashboard'
|
|
87
|
+
params.delete('view')
|
|
88
|
+
|
|
74
89
|
const search = params.toString()
|
|
90
|
+
const path = view !== 'dashboard' ? `/${view}` : '/'
|
|
75
91
|
const newUrl = search
|
|
76
|
-
? `${
|
|
77
|
-
: `${
|
|
92
|
+
? `${path}?${search}${window.location.hash}`
|
|
93
|
+
: `${path}${window.location.hash}`
|
|
78
94
|
// 📖 replaceState (not pushState) so back/forward don't fill up with
|
|
79
95
|
// 📖 every keystroke. The current URL still updates visibly.
|
|
80
96
|
window.history.replaceState({}, '', newUrl)
|