mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
docs/tutorials-batch-1
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5fe0843eb2 |
feat: add routable browser URLs for stacks and shell views (#1586)
* feat: add routable browser URLs for stacks and shell views Sync in-memory navigation to the address bar via a History API hook so deep links, refresh, Back/Forward, and bookmarks work across nodes, views, stack editor tabs, and mobile surfaces. Gate role/tier URL normalization on permissions and license readiness, preserve URLs on metadata fetch failure, and surface retryable stack-list errors without rewriting pending stack paths. * fix: preserve deep-link views on cold load and refresh Stop the node-switch effect from resetting to dashboard on initial mount. Defer URL writer settlement until hydrated activeView matches the route. Adds E2E coverage for shell cold loads, stack refresh, and compose env tab. * fix: keep mobile dashboard on list surface so sidebar renders On mobile, the URL sync hook was routing /nodes/<slug>/dashboard to the content surface, hiding the stack list sidebar. This prevented the data-stacks-loaded sentinel from appearing, causing sidebar truncation E2E tests to time out after reload on a mobile viewport. Mobile dashboard now stays on the list surface; other non-editor views still render on the content surface. * fix: complete mobile URL routing follow-ups for stack deep links Restore mobile /dashboard vs /stacks, list surface always writes /stacks. Hydrate pendingDetailStack, freeze compose failures with routeDetailError, and add unit plus E2E coverage. * fix: hydrate shell views from URL and sync in-app navigation Bootstrap activeView and tab state from the pathname on cold load. Settle route phase when state already matches, normalize unknown segments, and open Monaco editor tabs from stack deep links via applyEditorRouteState. * fix: prevent mobile stack deep links from hanging on cold load The resolvePendingStack effect did not re-fire when the pending stack ref was populated during URL hydration, because the urlHydratingStack state set in the same callback was not listed in the effect's dependency array. Adding it causes the effect to retry once hydration has committed. A resolvingRef mutex prevents concurrent invocations. When the target file is already loaded, route state is applied directly without calling loadFileForRoute, which avoids unmounting the editor (and hiding the recovery chip) if a background refresh triggers route resolution during a deploy operation. * test: adapt stack, deploy, and sidebar e2e specs to routable stack URLs * ci: raise E2E Playwright job timeout to 20 minutes |
||
|
|
37e6e48b40 |
feat(files): copy & duplicate, bulk actions, disk-backed uploads, and an accessible file tree (#1409)
* perf(files): spool uploads to disk instead of buffering in memory
Switch the stack file-explorer upload from multer memoryStorage to
diskStorage and stream the spooled temp file through the file-root
gateway, so an upload is never held fully in RAM. Authorization and
root resolution now run before multer spools, so an unauthorized or
read-only-root request is rejected without writing a temp file, and the
spool is removed on every exit path. The named-volume helper write
verifies the written byte count, since cat cannot report a short write.
* feat(files): copy and duplicate files in the explorer
Add a copy capability to the stack file explorer: a same-folder
Duplicate (auto-suffixed name) and a "Copy to..." destination picker,
on both filesystem and named-volume roots. Copying is within-root,
symlink-leaf-safe, blocks a directory copy into its own subtree, and
refuses to create a protected name (compose/.env) at the stack root
while still allowing a protected file to be duplicated under a new name.
* feat(files): make the file tree keyboard accessible
Bring the stack file explorer tree to the WCAG tree pattern: rows are
treeitems carrying aria-level, aria-selected, and aria-expanded, with a
single roving tabindex and full keyboard navigation (arrow keys,
Home/End, Enter/Space) over a flattened visible-node list that stays in
lockstep with the rendered rows. A polite live region announces the
selected file. No visual change to the tree.
* feat(files): bulk select, delete, move, and download files
Add multi-select to the stack file explorer (checkboxes plus Shift and
Ctrl/Cmd click over the visible order) driving three bulk actions:
delete, move, and download as a streamed .tar.gz. All run within the
active root on both filesystem and named-volume backends, report
per-item results so partial failures surface (with the failed items
kept selected for retry), normalize ancestor/descendant selections
server-side, and cap the archive entry and byte counts before any
bytes are streamed. Protected compose/.env files are excluded from
bulk delete and move but may still be downloaded.
* docs(files): document copy, bulk actions, and keyboard navigation
Add the copy/duplicate and multi-select bulk delete/move/download
sections to the Files & Volumes page, a keyboard-navigation note for the
tree, an updated context-menu reference, and bulk troubleshooting entries.
* fix(files): inline path-injection barriers at the new file-op sinks
CodeQL js/path-injection does not credit the wrapped isPathWithinBase
containment check, so the new copy/bulk/disk-upload flows tripped the
gate. Inline the canonical path.resolve + startsWith barrier at the
realpath sink in resolveSafePathWithin (covers every user-relPath flow)
and confirm the multer spool path resolves within UPLOAD_TMP_DIR before
unlinking it or streaming it onward. Behavior is unchanged; the paths
were already validated.
* fix(files): guard the ancestor-walk realpath sink too
The first barrier covered realpath(target), but the ENOENT ancestor
walk re-derives the path via path.dirname, which static analysis treats
as a fresh tainted value. Add the same inline containment barrier before
that realpath and resolve the root case via the untainted base, so the
only tainted realpath input is one the startsWith check has cleared.
Behavior is unchanged.
* fix(files): resolve the root case off the taint path in the ancestor walk
The compound guard on existing (the same variable as the startsWith
subject) was not credited as a sanitizer. Handle the root case before
the barrier by resolving the untainted base directly, leaving a plain
canonical startsWith guard on the strictly-within ancestor. Behavior is
unchanged.
* fix(files): harden helper-backend bulk download and uploads
Address three issues found in the named-volume (helper) backend:
- Bulk download could send 200 headers before discovering a file the
helper download path refuses, tearing the archive mid-stream. The
prewalk now rejects symlinks, non-regular ("other") entries, and
files over the per-file download cap before any header (400/413).
FileEntry gains an 'other' type so non-regular entries stay distinct
from regular files as they pass through the gateway.
- The helper directory listing was fully buffered before the archive
entry cap could fire. listDir now accepts a limit; the list script
stops after limit+1 rows and the gateway reports truncation.
- A stdin pipeline error during a helper upload masked the container's
real nonzero exit code (and its 4xx mapping) as a generic 500. The
nonzero exit now wins; the masked stream error is logged.
* feat(files): add a New file toolbar button with server-enforced create-only
The stack file explorer could create a folder from a toolbar button but a
new file only from a folder's right-click menu, so a file could not be
created at the stack root at all. Add a New file toolbar button beside New
folder, targeting the current directory.
Creating a file now routes through a new createEmptyStackFile helper that
posts a zero-byte file through the existing upload endpoint with overwrite
off, so the server's exclusive-create path rejects an existing name instead
of clobbering it. A file collision surfaces inline in the dialog; a folder
collision and other failures surface as a toast.
* fix(files): widen the tree row hit area and add horizontal scroll for long names
Right-clicking a file tree row only opened the Sencho context menu when the
click landed on the filename; the rest of the row fell through to the native
browser menu, and long names were truncated with no way to read them.
Make each row span the full pane width (and grow with its content) so the
whole row is the context-menu trigger, and let the tree scroll horizontally
so a long name is reachable instead of clipped. A new opt-in horizontal prop
on ScrollArea adds the styled horizontal scrollbar without clamping content
width.
* docs(files): document the New file button, full-row right-click, and long-name scrolling
* test(files): cover createEmptyStackFile targeting the stack root
Add an API-layer case for the empty-directory (stack root) create path, the
primary reason the New file toolbar button exists, so a regression in the
root-level URL would be caught at unit speed rather than only in e2e.
|
||
|
|
55737ab34d |
test(stack-files): end-to-end coverage for the file explorer audit surface (#1218)
* test(stack-files): add end-to-end coverage for the file explorer audit surface Companion to e2e/stack-files.spec.ts, which already pins the basic admin and community-tier upload/edit/delete/download flows. The new spec adds the harder-to-reach surfaces the audit flagged: - Path-traversal corpus on GET /files: nine variants ( .. , ../etc, a/../b , absolute POSIX, Windows drive, backslash, double-slash, NUL byte, a/./b ) each asserted to return 400 INVALID_PATH. - Protected-file enforcement via direct API: compose.yaml and .env delete return 409 PROTECTED_FILE; a subdirectory entry named compose.yaml still deletes (root-scope guarantee preserved). - Optimistic concurrency: two writers race on the same file via If-Match headers; the loser sees 412 PRECONDITION_FAILED with the current content payload, and the winner's bytes are on disk. - Large directory truncation: 1100 entries surface 1000 in the body with X-Truncated: true and X-Total-Count headers intact. - Binary detection and force=text override: a UTF-8 file carrying a NUL byte returns binary:true by default and is rescued via ?force=text. Oversized files keep the no-inline-content contract even when force=text is set. - 25 MB upload cap: a 25 MB + 1 byte payload trips the multer LIMIT_FILE_SIZE branch and returns 413 TOO_LARGE. - Symlink semantics: DELETE removes only the link entry and leaves the target intact; PUT permissions on a symlink returns 409 LINK_CHMOD_UNSUPPORTED with the target mode unchanged. - UI lifecycle: upload via the dropzone then API rename, and API mkdir; both verify the resulting tree state by reloading the Files tab. Four deferred surfaces are declared as test.skip with a one-line rationale each: the remote-node matrix and mid-op disconnect (require a real peer enrolled in CI), the developer-mode diagnostic matrix (no stable hook for backend stdout in Playwright; covered at the route-test layer), and the viewer-role tier-persona matrix (role gating is covered at the route-test layer). * test(stack-files): trim memory-heavy cases out of the E2E spec CI ran the spec end-to-end and the run completed, but the two heaviest cases (1100-file directory seed and a 25 MB upload buffer) pushed the single-worker Playwright queue past the dashboard-render budget for the specs that followed. stack-files.spec.ts and stacks.spec.ts saw their loginAs await time out at 10 s while the post-spec sidebar was still loading. Both behaviours are exercised at the backend route-test layer (stack-files-routes.test.ts pins the 1000-entry truncation header on a 1100-file directory and the multer LIMIT_FILE_SIZE 413 with a 26 MB buffer), so deleting them from the E2E spec loses nothing material. The oversized force=text test is also trimmed from 2.5 MB to 2.1 MB, which still exercises the >2 MB branch on the backend. The deferred-coverage block now lists the two excised cases with the rationale for why E2E is the wrong layer for them. * test(stack-files): collapse 7 per-describe seed hooks into 1 file-scope pair Each inner describe previously ran its own seedSuite + teardownSuite pair: 7 logins, 7 stack creates, 7 stack deletes, all serial. That added 15-20 seconds of CI setup overhead on a single-worker run and pushed the post-spec dashboard load past the 10 s budget some downstream specs allow on their loginAs await. Moving the seed/teardown to file scope keeps the test stack present for every test in this file with one fixture pair. Each test still calls loginAs in its beforeEach so the page state is fresh, but the expensive setup runs once instead of seven times. |