treesap 0.1.8 → 0.1.10

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.
Files changed (43) hide show
  1. package/dist/components/Sidebar.d.ts +8 -0
  2. package/dist/components/Sidebar.d.ts.map +1 -0
  3. package/dist/components/Sidebar.js +6 -0
  4. package/dist/components/Sidebar.js.map +1 -0
  5. package/dist/components/SimpleLivePreview.js +1 -1
  6. package/dist/components/SimpleLivePreview.js.map +1 -1
  7. package/dist/layouts/Layout.js +1 -1
  8. package/dist/layouts/Layout.js.map +1 -1
  9. package/dist/pages/Code.d.ts.map +1 -1
  10. package/dist/pages/Code.js +2 -2
  11. package/dist/pages/Code.js.map +1 -1
  12. package/dist/server.d.ts.map +1 -1
  13. package/dist/server.js +81 -11
  14. package/dist/server.js.map +1 -1
  15. package/dist/services/terminal.d.ts +25 -1
  16. package/dist/services/terminal.d.ts.map +1 -1
  17. package/dist/services/terminal.js +135 -6
  18. package/dist/services/terminal.js.map +1 -1
  19. package/dist/services/websocket.d.ts +45 -0
  20. package/dist/services/websocket.d.ts.map +1 -0
  21. package/dist/services/websocket.js +306 -0
  22. package/dist/services/websocket.js.map +1 -0
  23. package/dist/static/components/Sidebar.js +225 -0
  24. package/dist/static/components/SimpleLivePreview.js +73 -53
  25. package/dist/static/components/Terminal.js +141 -61
  26. package/dist/static/signals/SidebarSignal.js +123 -0
  27. package/dist/static/signals/TerminalSignal.js +137 -2
  28. package/dist/static/styles/main.css +111 -25
  29. package/package.json +6 -2
  30. package/src/components/Sidebar.tsx +92 -0
  31. package/src/components/SimpleLivePreview.tsx +4 -4
  32. package/src/layouts/Layout.tsx +1 -1
  33. package/src/pages/Code.tsx +18 -145
  34. package/src/server.tsx +97 -12
  35. package/src/services/terminal.ts +164 -6
  36. package/src/services/websocket.ts +374 -0
  37. package/src/static/components/Sidebar.js +225 -0
  38. package/src/static/components/SimpleLivePreview.js +73 -53
  39. package/src/static/components/Terminal.js +141 -61
  40. package/src/static/signals/SidebarSignal.js +123 -0
  41. package/src/static/signals/TerminalSignal.js +137 -2
  42. package/src/static/styles/main.css +111 -25
  43. package/tailwind.config.ts +10 -0
@@ -1,18 +1,28 @@
1
1
  import { signal, computed } from 'https://esm.sh/@preact/signals@1.2.2';
2
2
 
3
- // Terminal state management for multiple terminals
3
+ // Terminal state management for multiple terminals with cross-tab sync
4
4
  class TerminalStore {
5
5
  constructor() {
6
6
  // Core state signals
7
- this.terminals = signal([]); // Array of terminal objects: {id, sessionId, status, index}
7
+ this.terminals = signal([]); // Array of terminal objects: {id, sessionId, status, index, clientCount}
8
8
  this.activeTerminalId = signal('terminal-1'); // Currently active terminal
9
9
  this.nextTerminalIndex = signal(1); // For generating new terminal indices
10
10
 
11
+ // Cross-tab sync signals
12
+ this.sessionClients = signal(new Map()); // sessionId -> client count
13
+ this.globalStatus = signal('initializing'); // Overall connection status
14
+
11
15
  // Computed values
12
16
  this.activeTerminal = computed(() =>
13
17
  this.terminals.value.find(t => t.id === this.activeTerminalId.value)
14
18
  );
15
19
  this.terminalCount = computed(() => this.terminals.value.length);
20
+ this.hasConnectedTerminals = computed(() =>
21
+ this.terminals.value.some(t => t.status === 'connected')
22
+ );
23
+
24
+ // Initialize cross-tab communication
25
+ this.initializeCrossTabSync();
16
26
  }
17
27
 
18
28
  // Actions for managing terminals
@@ -118,6 +128,131 @@ class TerminalStore {
118
128
  console.log('Terminal Store State:', this.getState());
119
129
  }
120
130
 
131
+ // Initialize cross-tab communication
132
+ initializeCrossTabSync() {
133
+ // Listen for WebSocket events from terminal managers
134
+ this.setupWebSocketEventListeners();
135
+
136
+ // Listen for browser storage events for cross-tab sync
137
+ this.setupStorageEventListeners();
138
+
139
+ // Initialize from any existing state in localStorage
140
+ this.loadStateFromStorage();
141
+ }
142
+
143
+ setupWebSocketEventListeners() {
144
+ // Custom events dispatched by TerminalManager
145
+ document.addEventListener('terminal:clients_count', (event) => {
146
+ this.updateSessionClientCount(event.detail.sessionId, event.detail.count);
147
+ });
148
+
149
+ document.addEventListener('terminal:session_closed', (event) => {
150
+ this.handleSessionClosed(event.detail.sessionId);
151
+ });
152
+
153
+ document.addEventListener('terminal:global_status', (event) => {
154
+ this.globalStatus.value = event.detail.status;
155
+ });
156
+ }
157
+
158
+ setupStorageEventListeners() {
159
+ // Listen for changes from other tabs
160
+ window.addEventListener('storage', (event) => {
161
+ if (event.key === 'treesap_terminal_state') {
162
+ this.syncFromStorage(event.newValue);
163
+ }
164
+ });
165
+
166
+ // Save state to storage when terminals change
167
+ this.terminals.subscribe(() => {
168
+ this.saveStateToStorage();
169
+ });
170
+ }
171
+
172
+ loadStateFromStorage() {
173
+ try {
174
+ const savedState = localStorage.getItem('treesap_terminal_state');
175
+ if (savedState) {
176
+ const state = JSON.parse(savedState);
177
+ // Only sync certain properties, not connection status which is per-tab
178
+ if (state.activeTerminalId) {
179
+ this.activeTerminalId.value = state.activeTerminalId;
180
+ }
181
+ if (state.nextTerminalIndex) {
182
+ this.nextTerminalIndex.value = state.nextTerminalIndex;
183
+ }
184
+ }
185
+ } catch (error) {
186
+ console.error('Error loading terminal state from storage:', error);
187
+ }
188
+ }
189
+
190
+ saveStateToStorage() {
191
+ try {
192
+ const state = {
193
+ activeTerminalId: this.activeTerminalId.value,
194
+ nextTerminalIndex: this.nextTerminalIndex.value,
195
+ timestamp: Date.now()
196
+ };
197
+ localStorage.setItem('treesap_terminal_state', JSON.stringify(state));
198
+ } catch (error) {
199
+ console.error('Error saving terminal state to storage:', error);
200
+ }
201
+ }
202
+
203
+ syncFromStorage(newValueStr) {
204
+ if (!newValueStr) return;
205
+
206
+ try {
207
+ const newState = JSON.parse(newValueStr);
208
+ // Only sync if the change is from another tab (newer timestamp)
209
+ const currentTimestamp = this.lastSaveTimestamp || 0;
210
+ if (newState.timestamp && newState.timestamp > currentTimestamp) {
211
+ if (newState.activeTerminalId) {
212
+ this.activeTerminalId.value = newState.activeTerminalId;
213
+ }
214
+ if (newState.nextTerminalIndex) {
215
+ this.nextTerminalIndex.value = newState.nextTerminalIndex;
216
+ }
217
+ }
218
+ } catch (error) {
219
+ console.error('Error syncing terminal state from storage:', error);
220
+ }
221
+ }
222
+
223
+ updateSessionClientCount(sessionId, count) {
224
+ const currentMap = new Map(this.sessionClients.value);
225
+ currentMap.set(sessionId, count);
226
+ this.sessionClients.value = currentMap;
227
+
228
+ // Update terminal objects with client count
229
+ this.terminals.value = this.terminals.value.map(terminal => {
230
+ if (terminal.sessionId === sessionId) {
231
+ return { ...terminal, clientCount: count, lastUpdated: new Date() };
232
+ }
233
+ return terminal;
234
+ });
235
+
236
+ console.log(`Session ${sessionId} now has ${count} clients`);
237
+ }
238
+
239
+ handleSessionClosed(sessionId) {
240
+ console.log(`Session ${sessionId} was closed`);
241
+
242
+ // Update terminals for this session
243
+ this.terminals.value = this.terminals.value.map(terminal => {
244
+ if (terminal.sessionId === sessionId) {
245
+ return { ...terminal, status: 'closed', lastUpdated: new Date() };
246
+ }
247
+ return terminal;
248
+ });
249
+
250
+ // Remove from session clients
251
+ const currentMap = new Map(this.sessionClients.value);
252
+ currentMap.delete(sessionId);
253
+ this.sessionClients.value = currentMap;
254
+ }
255
+
121
256
  // Initialize default terminal
122
257
  initializeDefaultTerminal() {
123
258
  if (this.terminals.value.length === 0) {
@@ -1093,6 +1093,10 @@ video {
1093
1093
  margin-bottom: 0;
1094
1094
  }
1095
1095
 
1096
+ .pointer-events-none {
1097
+ pointer-events: none;
1098
+ }
1099
+
1096
1100
  .visible {
1097
1101
  visibility: visible;
1098
1102
  }
@@ -1101,6 +1105,10 @@ video {
1101
1105
  position: static;
1102
1106
  }
1103
1107
 
1108
+ .fixed {
1109
+ position: fixed;
1110
+ }
1111
+
1104
1112
  .absolute {
1105
1113
  position: absolute;
1106
1114
  }
@@ -1109,10 +1117,38 @@ video {
1109
1117
  position: relative;
1110
1118
  }
1111
1119
 
1120
+ .inset-0 {
1121
+ inset: 0px;
1122
+ }
1123
+
1124
+ .left-0 {
1125
+ left: 0px;
1126
+ }
1127
+
1128
+ .left-4 {
1129
+ left: 1rem;
1130
+ }
1131
+
1132
+ .top-0 {
1133
+ top: 0px;
1134
+ }
1135
+
1136
+ .top-4 {
1137
+ top: 1rem;
1138
+ }
1139
+
1140
+ .z-40 {
1141
+ z-index: 40;
1142
+ }
1143
+
1112
1144
  .z-50 {
1113
1145
  z-index: 50;
1114
1146
  }
1115
1147
 
1148
+ .z-60 {
1149
+ z-index: 60;
1150
+ }
1151
+
1116
1152
  .mb-12 {
1117
1153
  margin-bottom: 3rem;
1118
1154
  }
@@ -1141,10 +1177,6 @@ video {
1141
1177
  margin-left: 0.5rem;
1142
1178
  }
1143
1179
 
1144
- .mr-1 {
1145
- margin-right: 0.25rem;
1146
- }
1147
-
1148
1180
  .mr-2 {
1149
1181
  margin-right: 0.5rem;
1150
1182
  }
@@ -1221,6 +1253,20 @@ video {
1221
1253
  flex-shrink: 0;
1222
1254
  }
1223
1255
 
1256
+ .-translate-x-full {
1257
+ --tw-translate-x: -100%;
1258
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1259
+ }
1260
+
1261
+ .translate-x-0 {
1262
+ --tw-translate-x: 0px;
1263
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1264
+ }
1265
+
1266
+ .transform {
1267
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1268
+ }
1269
+
1224
1270
  @keyframes spin {
1225
1271
  to {
1226
1272
  transform: rotate(360deg);
@@ -1251,10 +1297,6 @@ video {
1251
1297
  justify-content: center;
1252
1298
  }
1253
1299
 
1254
- .gap-1 {
1255
- gap: 0.25rem;
1256
- }
1257
-
1258
1300
  .gap-2 {
1259
1301
  gap: 0.5rem;
1260
1302
  }
@@ -1367,6 +1409,10 @@ video {
1367
1409
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
1368
1410
  }
1369
1411
 
1412
+ .bg-opacity-50 {
1413
+ --tw-bg-opacity: 0.5;
1414
+ }
1415
+
1370
1416
  .bg-gradient-to-br {
1371
1417
  background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
1372
1418
  }
@@ -1381,10 +1427,6 @@ video {
1381
1427
  --tw-gradient-to: #16a34a var(--tw-gradient-to-position);
1382
1428
  }
1383
1429
 
1384
- .p-0\.5 {
1385
- padding: 0.125rem;
1386
- }
1387
-
1388
1430
  .p-1 {
1389
1431
  padding: 0.25rem;
1390
1432
  }
@@ -1397,10 +1439,6 @@ video {
1397
1439
  padding: 0.75rem;
1398
1440
  }
1399
1441
 
1400
- .p-4 {
1401
- padding: 1rem;
1402
- }
1403
-
1404
1442
  .p-6 {
1405
1443
  padding: 1.5rem;
1406
1444
  }
@@ -1409,11 +1447,6 @@ video {
1409
1447
  padding: 2rem;
1410
1448
  }
1411
1449
 
1412
- .px-2 {
1413
- padding-left: 0.5rem;
1414
- padding-right: 0.5rem;
1415
- }
1416
-
1417
1450
  .px-3 {
1418
1451
  padding-left: 0.75rem;
1419
1452
  padding-right: 0.75rem;
@@ -1512,6 +1545,14 @@ video {
1512
1545
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1513
1546
  }
1514
1547
 
1548
+ .opacity-0 {
1549
+ opacity: 0;
1550
+ }
1551
+
1552
+ .opacity-100 {
1553
+ opacity: 1;
1554
+ }
1555
+
1515
1556
  .opacity-50 {
1516
1557
  opacity: 0.5;
1517
1558
  }
@@ -1526,6 +1567,12 @@ video {
1526
1567
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1527
1568
  }
1528
1569
 
1570
+ .backdrop-blur-sm {
1571
+ --tw-backdrop-blur: blur(4px);
1572
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1573
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
1574
+ }
1575
+
1529
1576
  .transition-all {
1530
1577
  transition-property: all;
1531
1578
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -1538,10 +1585,26 @@ video {
1538
1585
  transition-duration: 150ms;
1539
1586
  }
1540
1587
 
1588
+ .transition-opacity {
1589
+ transition-property: opacity;
1590
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1591
+ transition-duration: 150ms;
1592
+ }
1593
+
1594
+ .transition-transform {
1595
+ transition-property: transform;
1596
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1597
+ transition-duration: 150ms;
1598
+ }
1599
+
1541
1600
  .duration-300 {
1542
1601
  transition-duration: 300ms;
1543
1602
  }
1544
1603
 
1604
+ .ease-in-out {
1605
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1606
+ }
1607
+
1545
1608
  .focus-within\:border-\[\#0e639c\]:focus-within {
1546
1609
  --tw-border-opacity: 1;
1547
1610
  border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
@@ -1593,11 +1656,34 @@ video {
1593
1656
  outline-offset: 2px;
1594
1657
  }
1595
1658
 
1596
- .disabled\:opacity-50:disabled {
1597
- opacity: 0.5;
1598
- }
1599
-
1600
1659
  .group:hover .group-hover\:text-white {
1601
1660
  --tw-text-opacity: 1;
1602
1661
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1603
1662
  }
1663
+
1664
+ @media (min-width: 768px) {
1665
+ .md\:relative {
1666
+ position: relative;
1667
+ }
1668
+
1669
+ .md\:z-auto {
1670
+ z-index: auto;
1671
+ }
1672
+
1673
+ .md\:hidden {
1674
+ display: none;
1675
+ }
1676
+
1677
+ .md\:w-2\/5 {
1678
+ width: 40%;
1679
+ }
1680
+
1681
+ .md\:flex-1 {
1682
+ flex: 1 1 0%;
1683
+ }
1684
+
1685
+ .md\:translate-x-0 {
1686
+ --tw-translate-x: 0px;
1687
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1688
+ }
1689
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "treesap",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "AI Agent Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,10 +37,14 @@
37
37
  "commander": "^12.1.0",
38
38
  "dotenv": "^16.4.7",
39
39
  "hono": "^4.9.1",
40
- "node-pty": "^1.1.0-beta34"
40
+ "node-pty": "^1.1.0-beta34",
41
+ "uuid": "^11.1.0",
42
+ "ws": "^8.18.3"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/node": "^22.15.15",
46
+ "@types/uuid": "^10.0.0",
47
+ "@types/ws": "^8.18.1",
44
48
  "tailwindcss": "^3.4.17",
45
49
  "tsx": "^4.19.2",
46
50
  "typescript": "^5.7.3"
@@ -0,0 +1,92 @@
1
+ import { Terminal as TerminalComponent } from "./Terminal.js";
2
+
3
+ interface SidebarProps {
4
+ id?: string;
5
+ previewPort?: number;
6
+ workingDirectory?: string;
7
+ }
8
+
9
+ export function Sidebar({ id = "sidebar", previewPort = 1234, workingDirectory }: SidebarProps) {
10
+ return (
11
+ <sapling-island loading="visible">
12
+ <template>
13
+ <script type="module" src="https://code.iconify.design/iconify-icon/2.0.0/iconify-icon.min.js"></script>
14
+ <script type="module" src="/components/Sidebar.js"></script>
15
+ </template>
16
+
17
+ {/* Mobile backdrop */}
18
+ <div
19
+ id={`${id}-backdrop`}
20
+ class="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-40 transition-opacity duration-300 opacity-0 pointer-events-none md:hidden"
21
+ ></div>
22
+
23
+ {/* Sidebar container */}
24
+ <div
25
+ id={`${id}-pane`}
26
+ class="fixed left-0 top-0 h-full w-full z-50 transform -translate-x-full transition-transform duration-300 ease-in-out md:relative md:translate-x-0 md:w-2/5 md:z-auto border-r border-[#3c3c3c] flex flex-col bg-[#252526]"
27
+ >
28
+ {/* Preview Controls */}
29
+ <div class="p-3 border-b border-[#3c3c3c] bg-[#2d2d30] flex-shrink-0">
30
+ <div class="flex items-center gap-2">
31
+ {/* Mobile close button */}
32
+ <button
33
+ type="button"
34
+ id={`${id}-close-btn`}
35
+ class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white md:hidden"
36
+ title="Close Sidebar"
37
+ >
38
+ <iconify-icon icon="tabler:x" width="16" height="16"></iconify-icon>
39
+ </button>
40
+
41
+ {/* Back to Home */}
42
+ <a
43
+ href="/"
44
+ class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
45
+ title="Back to Home"
46
+ >
47
+ <iconify-icon icon="tabler:arrow-left" width="16" height="16"></iconify-icon>
48
+ </a>
49
+
50
+ {/* Refresh button */}
51
+ <button
52
+ type="button"
53
+ id="live-preview-refresh-btn"
54
+ class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
55
+ title="Reload"
56
+ >
57
+ <iconify-icon icon="tabler:refresh" width="16" height="16"></iconify-icon>
58
+ </button>
59
+
60
+ {/* URL input */}
61
+ <div class="flex-1 flex items-center bg-[#1e1e1e] border border-[#3c3c3c] rounded px-3 py-2 hover:border-[#0e639c] focus-within:border-[#0e639c] transition-all">
62
+ <iconify-icon icon="tabler:world" width="16" height="16" class="text-[#cccccc] mr-2"></iconify-icon>
63
+ <span class="text-[#cccccc] text-sm">localhost:{previewPort}/</span>
64
+ <input
65
+ id="live-preview-url-input"
66
+ type="text"
67
+ placeholder="path"
68
+ defaultValue=""
69
+ class="flex-1 bg-transparent text-sm focus:outline-none text-[#cccccc] ml-1"
70
+ />
71
+ <button
72
+ type="button"
73
+ id="live-preview-load-btn"
74
+ class="ml-2 p-1 hover:bg-[#3c3c3c] rounded transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
75
+ title="Go"
76
+ >
77
+ <iconify-icon icon="tabler:chevron-right" width="16" height="16"></iconify-icon>
78
+ </button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ {/* Terminal Content */}
84
+ <div class="flex-1 overflow-hidden bg-[#1e1e1e]">
85
+ <div class="h-full">
86
+ <TerminalComponent index={1} />
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </sapling-island>
91
+ );
92
+ }
@@ -25,14 +25,14 @@ export function SimpleLivePreview({ id = "simple-preview", previewPort = 5173 }:
25
25
  data-preview-port={previewPort}
26
26
  ></iframe>
27
27
 
28
- {/* Floating Sidebar Toggle */}
28
+ {/* Floating Sidebar Toggle - Only show when sidebar is closed */}
29
29
  <button
30
30
  id={`${id}-floating-hide-sidebar-btn`}
31
- class="absolute p-3 bg-white border-2 border-gray-400 rounded-lg shadow-xl hover:bg-gray-50 hover:shadow-2xl transition-all flex items-center justify-center z-50"
32
- title="Toggle Sidebar"
31
+ class="absolute p-3 bg-white border-2 border-gray-400 rounded-lg shadow-xl hover:bg-gray-50 hover:shadow-2xl transition-all items-center justify-center z-50 hidden"
32
+ title="Show Sidebar"
33
33
  style="position: absolute !important; z-index: 9999 !important; bottom: 16px; left: 16px;"
34
34
  >
35
- <iconify-icon id={`${id}-floating-hide-sidebar-icon`} icon="ph:sidebar-simple" width="20" height="20" class="text-gray-800"></iconify-icon>
35
+ <iconify-icon id={`${id}-floating-hide-sidebar-icon`} icon="ph:sidebar-simple-fill" width="20" height="20" class="text-gray-800"></iconify-icon>
36
36
  </button>
37
37
  </div>
38
38
  </sapling-island>
@@ -14,7 +14,7 @@ export default function BaseLayout(props: BaseLayoutProps) {
14
14
  <title>{props.title || "Treesap"}</title>
15
15
  <meta name="description" content={props.description || "A modern web application"} />
16
16
  <meta charset="UTF-8" />
17
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
18
18
  <link rel="stylesheet" href="/styles/main.css" />
19
19
  {/* Sapling Islands */}
20
20
  <script type="module" src="https://sapling-is.land"></script>