@treeseed/core 0.8.9 → 0.8.11

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.
@@ -23,8 +23,8 @@ const { siteTitleHref } = Astro.locals.starlightRoute;
23
23
  }
24
24
 
25
25
  .site-header-logo {
26
- width: 2.5rem;
27
- height: 2.5rem;
26
+ width: 3.75rem;
27
+ height: 3.75rem;
28
28
  object-fit: contain;
29
29
  flex: 0 0 auto;
30
30
  }
@@ -3,11 +3,8 @@ import EditLink from '../../vendor/starlight/components/EditLink.astro';
3
3
  import LastUpdated from '../../vendor/starlight/components/LastUpdated.astro';
4
4
  import Pagination from '../../vendor/starlight/components/Pagination.astro';
5
5
  import config from 'virtual:starlight/user-config';
6
- import FooterSubscribeForm from '../forms/FooterSubscribeForm.astro';
6
+ import PublicFooter from '../ui/shell/PublicFooter.astro';
7
7
  import DevWatchReload from '../DevWatchReload.astro';
8
- import { PROJECT_STAGE } from '../../utils/content-status';
9
- import { SITE } from '../../utils/seo';
10
- import { SITE_FOOTER_MENU } from '../../utils/site-config';
11
8
  ---
12
9
 
13
10
  <div class="docs-footer-shell">
@@ -28,32 +25,7 @@ import { SITE_FOOTER_MENU } from '../../utils/site-config';
28
25
  </div>
29
26
  </div>
30
27
 
31
- <footer class="docs-site-footer">
32
- <div class="docs-site-footer-inner">
33
- <div class="docs-footer-grid">
34
- <div class="docs-footer-column">
35
- <p class="docs-footer-title">{SITE.name}</p>
36
- <p class="docs-footer-copy">{SITE.summary}</p>
37
- </div>
38
- <div class="docs-footer-column">
39
- <p class="docs-footer-heading">Project stage</p>
40
- <p class="docs-footer-column-title">{PROJECT_STAGE.label}</p>
41
- <p class="docs-footer-copy">{PROJECT_STAGE.description}</p>
42
- </div>
43
- {SITE_FOOTER_MENU.map((group) => (
44
- <div class="docs-footer-column">
45
- <p class="docs-footer-heading">{group.label}</p>
46
- <div class="docs-footer-links">
47
- {group.items.map((item) => (
48
- <a href={item.href}>{item.label}</a>
49
- ))}
50
- </div>
51
- </div>
52
- ))}
53
- </div>
54
- <FooterSubscribeForm currentPath={Astro.url.pathname} />
55
- </div>
56
- </footer>
28
+ <PublicFooter currentPath={Astro.url.pathname} />
57
29
  <DevWatchReload />
58
30
  </div>
59
31
 
@@ -65,7 +37,7 @@ import { SITE_FOOTER_MENU } from '../../utils/site-config';
65
37
  }
66
38
 
67
39
  .docs-footer-page-inner,
68
- .docs-site-footer-inner {
40
+ .docs-footer-shell :global(.ts-public-footer__inner) {
69
41
  width: min(100%, var(--ts-content-width));
70
42
  margin-inline: auto;
71
43
  padding-inline: 1rem;
@@ -119,76 +91,20 @@ import { SITE_FOOTER_MENU } from '../../utils/site-config';
119
91
  color: var(--sl-color-white);
120
92
  }
121
93
 
122
- .docs-site-footer {
94
+ .docs-footer-shell :global(.ts-public-footer) {
123
95
  margin-top: 0;
124
- border-top: 2px solid var(--ts-color-border-strong);
125
- padding-top: 2rem;
126
- }
127
-
128
- .docs-site-footer-inner {
129
- padding-bottom: 3.75rem;
130
- }
131
-
132
- .docs-footer-grid {
133
- display: flex;
134
- flex-wrap: wrap;
135
- justify-content: center;
136
- gap: 2rem;
137
- padding-bottom: 2rem;
138
- }
139
-
140
- .docs-footer-column {
141
- width: 16rem;
142
- }
143
-
144
- .docs-footer-heading {
145
- font-size: var(--sl-text-sm);
146
- font-weight: 600;
147
- letter-spacing: 0.16em;
148
- text-transform: uppercase;
149
- color: var(--ts-color-info-text);
150
- }
151
-
152
- .docs-footer-title,
153
- .docs-footer-column-title {
154
- font-size: 1.25rem;
155
- font-weight: 700;
156
- color: var(--ts-color-text);
157
- }
158
-
159
- .docs-footer-copy,
160
- .docs-footer-links {
161
- margin-top: 0.75rem;
162
- font-size: 1rem;
163
- line-height: 2;
164
- color: var(--ts-color-text-muted);
165
- }
166
-
167
- .docs-footer-links {
168
- display: flex;
169
- flex-direction: column;
170
- gap: 0.5rem;
171
- }
172
-
173
- .docs-footer-links a {
174
- text-decoration: none;
175
- }
176
-
177
- .docs-footer-links a:hover,
178
- .docs-footer-links a:focus-visible {
179
- color: var(--ts-color-text);
180
96
  }
181
97
 
182
98
  @media (min-width: 40rem) {
183
99
  .docs-footer-page-inner,
184
- .docs-site-footer-inner {
100
+ .docs-footer-shell :global(.ts-public-footer__inner) {
185
101
  padding-inline: 1.5rem;
186
102
  }
187
103
  }
188
104
 
189
105
  @media (min-width: 64rem) {
190
106
  .docs-footer-page-inner,
191
- .docs-site-footer-inner {
107
+ .docs-footer-shell :global(.ts-public-footer__inner) {
192
108
  padding-inline: 2rem;
193
109
  }
194
110
  }
@@ -3,7 +3,6 @@ import config from 'virtual:starlight/user-config';
3
3
 
4
4
  import LanguageSelect from '../../vendor/starlight/components/LanguageSelect.astro';
5
5
  import Search from '../../vendor/starlight/components/Search.astro';
6
- import SiteTitle from '../../vendor/starlight/components/SiteTitle.astro';
7
6
  import SocialIcons from '../../vendor/starlight/components/SocialIcons.astro';
8
7
  import DesktopSidebarToggle from './DesktopSidebarToggle.astro';
9
8
  import BookFontControls from './BookFontControls.astro';
@@ -18,7 +17,15 @@ const hasToc = Boolean(toc);
18
17
 
19
18
  <div class="header">
20
19
  <div class="title-wrapper sl-flex">
21
- <SiteTitle />
20
+ <a class="ts-shell-brand" href="/">
21
+ <span class="ts-shell-brand__mark">
22
+ <img src={SITE.logo.src} alt={SITE.logo.alt} width="60" height="60" loading="eager" />
23
+ </span>
24
+ <span class="ts-shell-brand__text">
25
+ <span class="ts-shell-brand__name">{SITE.name}</span>
26
+ <span class="ts-shell-brand__tag">{SITE.statement}</span>
27
+ </span>
28
+ </a>
22
29
  </div>
23
30
  <div class="sl-hidden md:sl-flex print:hidden right-group">
24
31
  <div class="desktop-sidebar-toggles" aria-label="Reading controls">
@@ -17,8 +17,8 @@ const {
17
17
  <a href="/" class="border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
18
18
  Go home
19
19
  </a>
20
- <a href="/knowledge/" class="border border-[color:var(--ts-color-border-strong)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
21
- Open knowledge
20
+ <a href="/books/" class="border border-[color:var(--ts-color-border-strong)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
21
+ Open books
22
22
  </a>
23
23
  </div>
24
24
  </section>
@@ -9,6 +9,7 @@ import RailNav from './RailNav.astro';
9
9
  import BottomNav from './BottomNav.astro';
10
10
  import TopBar from './TopBar.astro';
11
11
  import Button from '../forms/Button.astro';
12
+ import DevWatchReload from '../../DevWatchReload.astro';
12
13
  import type { ButtonAction } from '../types.js';
13
14
  import type { ThemePreference } from '../../../utils/theme.js';
14
15
 
@@ -106,5 +107,6 @@ const {
106
107
  {bottomNavItems.length > 0 ? <BottomNav items={bottomNavItems} currentPath={currentPath} /> : null}
107
108
  </div>
108
109
  <slot name="sensitiveModal" />
110
+ <DevWatchReload />
109
111
  </body>
110
112
  </html>
@@ -0,0 +1,39 @@
1
+ ---
2
+ import FooterSubscribeForm from '../../forms/FooterSubscribeForm.astro';
3
+ import { PROJECT_STAGE } from '../../../utils/content-status';
4
+ import { SITE } from '../../../utils/seo';
5
+ import { SITE_FOOTER_MENU } from '../../../utils/site-config';
6
+
7
+ interface Props {
8
+ currentPath?: string;
9
+ }
10
+
11
+ const { currentPath = Astro.url.pathname } = Astro.props as Props;
12
+ ---
13
+
14
+ <footer class="ts-public-footer">
15
+ <div class="ts-public-footer__inner">
16
+ <div class="ts-public-footer__grid">
17
+ <div class="ts-public-footer__column">
18
+ <p class="ts-public-footer__title">{SITE.name}</p>
19
+ <p class="ts-public-footer__copy">{SITE.summary}</p>
20
+ </div>
21
+ <div class="ts-public-footer__column">
22
+ <p class="ts-public-footer__heading">Project stage</p>
23
+ <p class="ts-public-footer__column-title">{PROJECT_STAGE.label}</p>
24
+ <p class="ts-public-footer__copy">{PROJECT_STAGE.description}</p>
25
+ </div>
26
+ {SITE_FOOTER_MENU.map((group) => (
27
+ <div class="ts-public-footer__column">
28
+ <p class="ts-public-footer__heading">{group.label}</p>
29
+ <div class="ts-public-footer__links">
30
+ {group.items.map((item) => (
31
+ <a href={item.href}>{item.label}</a>
32
+ ))}
33
+ </div>
34
+ </div>
35
+ ))}
36
+ </div>
37
+ <FooterSubscribeForm currentPath={currentPath} />
38
+ </div>
39
+ </footer>
@@ -8,6 +8,8 @@ import ThemeScript from '../theme/ThemeScript.astro';
8
8
  import ThemeMenu from '../theme/ThemeMenu.astro';
9
9
  import TopBar from './TopBar.astro';
10
10
  import Button from '../forms/Button.astro';
11
+ import PublicFooter from './PublicFooter.astro';
12
+ import DevWatchReload from '../../DevWatchReload.astro';
11
13
  import type { ButtonAction } from '../types.js';
12
14
  import type { ThemePreference } from '../../../utils/theme.js';
13
15
 
@@ -26,6 +28,11 @@ interface NavItem {
26
28
  ariaLabel?: string;
27
29
  }
28
30
 
31
+ interface NavGroup {
32
+ label: string;
33
+ items: NavItem[];
34
+ }
35
+
29
36
  interface Props {
30
37
  title: string;
31
38
  description: string;
@@ -33,9 +40,11 @@ interface Props {
33
40
  appearance: ThemePreference;
34
41
  brand: Brand;
35
42
  navItems: NavItem[];
43
+ navGroups?: NavGroup[];
36
44
  actions?: ButtonAction[];
37
45
  showAppearanceControl?: boolean;
38
46
  preferServerAppearance?: boolean;
47
+ contentWidth?: 'shell' | 'content';
39
48
  }
40
49
 
41
50
  const {
@@ -45,9 +54,11 @@ const {
45
54
  appearance,
46
55
  brand,
47
56
  navItems,
57
+ navGroups = [],
48
58
  actions = [],
49
59
  showAppearanceControl = true,
50
60
  preferServerAppearance = false,
61
+ contentWidth = 'shell',
51
62
  } = Astro.props as Props;
52
63
 
53
64
  function isCurrentPath(href: string) {
@@ -68,13 +79,49 @@ function isCurrentPath(href: string) {
68
79
  defaultMode={appearance.mode}
69
80
  preferDefaultPreference={preferServerAppearance}
70
81
  />
82
+ <slot name="head" />
71
83
  </head>
72
84
  <body>
73
85
  <a class="ts-skip-link" href="#main-content">Skip to content</a>
74
- <div class="ts-public-shell">
86
+ <div class:list={['ts-public-shell', contentWidth === 'content' && 'ts-public-shell--content']}>
75
87
  <header class="ts-public-shell__header">
76
88
  <TopBar brand={brand}>
77
89
  <Fragment slot="actions">
90
+ <nav class="ts-public-shell__nav" aria-label="Primary" data-ts-public-nav>
91
+ {navGroups.length > 0 ? (
92
+ navGroups.map((group) => (
93
+ <details class="ts-public-shell__nav-group" data-ts-public-nav-group>
94
+ <summary
95
+ class:list={[
96
+ 'ts-public-shell__link ts-public-shell__summary',
97
+ group.items.some((item) => isCurrentPath(item.href)) && 'ts-public-shell__summary--active',
98
+ ]}
99
+ >
100
+ {group.label}
101
+ <span aria-hidden="true">▾</span>
102
+ </summary>
103
+ <div class="ts-public-shell__menu">
104
+ {group.items.map((item) => (
105
+ <a
106
+ class="ts-public-shell__menu-link"
107
+ href={item.href}
108
+ aria-label={item.ariaLabel}
109
+ aria-current={isCurrentPath(item.href) ? 'page' : undefined}
110
+ >
111
+ {item.label}
112
+ </a>
113
+ ))}
114
+ </div>
115
+ </details>
116
+ ))
117
+ ) : (
118
+ navItems.map((item) => (
119
+ <a class="ts-public-shell__link" href={item.href} aria-label={item.ariaLabel} aria-current={isCurrentPath(item.href) ? 'page' : undefined}>
120
+ {item.label}
121
+ </a>
122
+ ))
123
+ )}
124
+ </nav>
78
125
  {showAppearanceControl ? <ThemeMenu selectedScheme={appearance.scheme} selectedMode={appearance.mode} /> : null}
79
126
  {actions.map((action) => (
80
127
  <Button
@@ -91,18 +138,42 @@ function isCurrentPath(href: string) {
91
138
  <slot name="actions" />
92
139
  </Fragment>
93
140
  </TopBar>
94
- <nav class="ts-public-shell__nav" aria-label="Primary">
95
- {navItems.map((item) => (
96
- <a class="ts-public-shell__link" href={item.href} aria-label={item.ariaLabel} aria-current={isCurrentPath(item.href) ? 'page' : undefined}>
97
- {item.label}
98
- </a>
99
- ))}
100
- </nav>
101
141
  </header>
102
142
  <main class="ts-public-shell__main" id="main-content">
103
143
  <slot />
104
144
  </main>
105
- <slot name="footer" />
145
+ {Astro.slots.has('footer') ? <slot name="footer" /> : <PublicFooter currentPath={currentPath} />}
146
+ <slot name="afterFooter" />
106
147
  </div>
148
+ <DevWatchReload />
149
+ <script>
150
+ const publicNav = document.querySelector('[data-ts-public-nav]');
151
+ const navGroups = document.querySelectorAll<HTMLDetailsElement>('[data-ts-public-nav-group]');
152
+
153
+ navGroups.forEach((group) => {
154
+ group.addEventListener('toggle', () => {
155
+ if (!group.open) return;
156
+ navGroups.forEach((otherGroup) => {
157
+ if (otherGroup !== group) otherGroup.open = false;
158
+ });
159
+ });
160
+ });
161
+
162
+ document.addEventListener('click', (event) => {
163
+ if (!(publicNav instanceof HTMLElement)) return;
164
+ if (event.target instanceof Node && publicNav.contains(event.target)) return;
165
+ navGroups.forEach((group) => {
166
+ group.open = false;
167
+ });
168
+ });
169
+
170
+ publicNav?.addEventListener('click', (event) => {
171
+ const target = event.target;
172
+ if (!(target instanceof HTMLElement) || !target.closest('a')) return;
173
+ navGroups.forEach((group) => {
174
+ group.open = false;
175
+ });
176
+ });
177
+ </script>
107
178
  </body>
108
179
  </html>
@@ -27,7 +27,7 @@ const {
27
27
  <div class:list={['ts-top-bar', className]}>
28
28
  <a class="ts-shell-brand" href={brand.href}>
29
29
  <span class="ts-shell-brand__mark">
30
- {brand.logoSrc ? <img src={brand.logoSrc} alt={brand.logoAlt ?? ''} width="40" height="40" loading="eager" /> : brand.mark}
30
+ {brand.logoSrc ? <img src={brand.logoSrc} alt={brand.logoAlt ?? ''} width="60" height="60" loading="eager" /> : brand.mark}
31
31
  </span>
32
32
  <span class="ts-shell-brand__text">
33
33
  <span class="ts-shell-brand__name">{brand.name}</span>
@@ -55,6 +55,11 @@ const themeCss = buildTreeseedThemeCss();
55
55
  }
56
56
  }
57
57
 
58
+ function cookie(name, value) {
59
+ const secure = window.location.protocol === 'https:' ? '; Secure' : '';
60
+ document.cookie = `${name}=${encodeURIComponent(value)}; Max-Age=${60 * 60 * 24 * 365}; Path=/; SameSite=Lax${secure}`;
61
+ }
62
+
58
63
  function store(name, value) {
59
64
  try {
60
65
  window.sessionStorage.setItem(name, value);
@@ -66,6 +71,7 @@ const themeCss = buildTreeseedThemeCss();
66
71
  } catch {
67
72
  // Local storage can be disabled; cookies still carry the preference for SSR.
68
73
  }
74
+ cookie(name, value);
69
75
  }
70
76
 
71
77
  function resolveScheme(value) {
@@ -81,8 +87,8 @@ const themeCss = buildTreeseedThemeCss();
81
87
  return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
82
88
  }
83
89
 
84
- const storedScheme = readStored(schemeKey) || readCookie(schemeKey);
85
- const storedModeValue = readStored(modeKey) || readCookie(modeKey);
90
+ const storedScheme = readCookie(schemeKey) || readStored(schemeKey);
91
+ const storedModeValue = readCookie(modeKey) || readStored(modeKey);
86
92
  const scheme = preferDefaultPreference ? resolveScheme(defaultScheme) : resolveScheme(storedScheme || defaultScheme);
87
93
  const storedMode = preferDefaultPreference ? resolveMode(defaultMode) : resolveMode(storedModeValue || defaultMode);
88
94
  const renderedMode = resolveRenderedMode(storedMode);
@@ -1,13 +1,17 @@
1
1
  export type TreeseedDevWatchEntry = {
2
- kind: 'tenant' | 'package' | 'sdk';
2
+ kind: 'tenant' | 'core' | 'sdk' | 'agent' | 'cli';
3
3
  root: string;
4
+ restartRequired?: boolean;
4
5
  };
5
6
  export type TreeseedDevWatchChange = {
6
7
  changedPaths: string[];
7
8
  tenantChanged: boolean;
8
9
  tenantApiChanged: boolean;
9
- packageChanged: boolean;
10
+ coreChanged: boolean;
10
11
  sdkChanged: boolean;
12
+ agentChanged: boolean;
13
+ cliChanged: boolean;
14
+ commandImplementationChanged: boolean;
11
15
  };
12
16
  export type TreeseedDevWatchController = {
13
17
  stop: () => void;
package/dist/dev-watch.js CHANGED
@@ -95,11 +95,20 @@ function classifyChanges(changedPaths, watchEntries) {
95
95
  sdkChanged: changedPaths.some(
96
96
  (filePath) => watchEntries.some((entry) => entry.kind === "sdk" && matchesEntry(filePath, entry))
97
97
  ),
98
- packageChanged: changedPaths.some(
99
- (filePath) => watchEntries.some((entry) => entry.kind === "package" && matchesEntry(filePath, entry))
98
+ coreChanged: changedPaths.some(
99
+ (filePath) => watchEntries.some((entry) => entry.kind === "core" && matchesEntry(filePath, entry))
100
+ ),
101
+ agentChanged: changedPaths.some(
102
+ (filePath) => watchEntries.some((entry) => entry.kind === "agent" && matchesEntry(filePath, entry))
103
+ ),
104
+ cliChanged: changedPaths.some(
105
+ (filePath) => watchEntries.some((entry) => entry.kind === "cli" && matchesEntry(filePath, entry))
100
106
  ),
101
107
  tenantChanged,
102
- tenantApiChanged: tenantChanged && changedPaths.some(isTenantApiInput)
108
+ tenantApiChanged: tenantChanged && changedPaths.some(isTenantApiInput),
109
+ commandImplementationChanged: changedPaths.some(
110
+ (filePath) => watchEntries.some((entry) => entry.restartRequired === true && matchesEntry(filePath, entry))
111
+ )
103
112
  };
104
113
  }
105
114
  function startPollingWatch({ watchEntries, onChange }) {
package/dist/dev.d.ts CHANGED
@@ -8,12 +8,13 @@ export declare const TREESEED_DEFAULT_API_PORT = 3000;
8
8
  export declare const TREESEED_DEFAULT_LOCAL_SMTP_HOST = "127.0.0.1";
9
9
  export declare const TREESEED_DEFAULT_LOCAL_SMTP_PORT = 1025;
10
10
  export declare const TREESEED_DEFAULT_MAILPIT_UI_PORT = 8025;
11
- export type TreeseedIntegratedDevSurface = 'integrated' | 'web';
11
+ export type TreeseedIntegratedDevSurface = 'integrated' | 'all' | 'web' | 'api' | 'manager' | 'worker' | 'agents' | 'services';
12
12
  export type TreeseedIntegratedDevSetupMode = 'auto' | 'check' | 'off';
13
13
  export type TreeseedIntegratedDevFeedbackMode = 'live' | 'restart' | 'off';
14
14
  export type TreeseedIntegratedDevOpenMode = 'auto' | 'on' | 'off';
15
15
  export type TreeseedLocalRuntimeMode = 'auto' | 'provider' | 'local';
16
16
  export type TreeseedSelectedLocalRuntime = 'astro-local' | 'cloudflare-wrangler-local' | 'node-local';
17
+ export type TreeseedIntegratedDevCommandId = 'web' | 'api' | 'manager' | 'worker' | 'agents';
17
18
  export type TreeseedLocalRuntimeSelection = {
18
19
  requested: TreeseedLocalRuntimeMode;
19
20
  selected: TreeseedSelectedLocalRuntime;
@@ -22,6 +23,7 @@ export type TreeseedLocalRuntimeSelection = {
22
23
  };
23
24
  export type TreeseedIntegratedDevOptions = {
24
25
  surface?: TreeseedIntegratedDevSurface;
26
+ surfaces?: string;
25
27
  watch?: boolean;
26
28
  cwd?: string;
27
29
  stdio?: SpawnOptions['stdio'];
@@ -44,7 +46,7 @@ export type TreeseedIntegratedDevOptions = {
44
46
  shutdownGraceMs?: number;
45
47
  };
46
48
  export type TreeseedIntegratedDevCommand = {
47
- id: 'web';
49
+ id: TreeseedIntegratedDevCommandId;
48
50
  label: string;
49
51
  command: string;
50
52
  args: string[];
@@ -96,6 +98,13 @@ export type TreeseedIntegratedDevPlan = {
96
98
  watchEntries: TreeseedIntegratedDevWatchEntry[];
97
99
  commands: TreeseedIntegratedDevCommand[];
98
100
  localRuntimes: Record<string, TreeseedLocalRuntimeSelection>;
101
+ restartPolicy: {
102
+ initialBackoffMs: number;
103
+ maxBackoffMs: number;
104
+ setupRetryBackoffMs: number;
105
+ commandImplementationChangesRequireRestart: boolean;
106
+ agentChanges: 'defer';
107
+ };
99
108
  reset: TreeseedIntegratedDevResetPlan | null;
100
109
  };
101
110
  type SpawnLike = (command: string, args: string[], options: SpawnOptions) => ChildProcess;