diff --git a/db/migrations/20260310094104_initial.sql b/db/migrations/20260310094104_initial.sql
index 6c8afc4..22544a5 100644
--- a/db/migrations/20260310094104_initial.sql
+++ b/db/migrations/20260310094104_initial.sql
@@ -171,7 +171,7 @@ CREATE TABLE audit_logs (
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-CREATE INDEX idx_audit_logs_team_time ON audit_logs(team_id, created_at DESC);
+CREATE INDEX idx_audit_logs_team_time ON audit_logs(team_id, created_at DESC, id DESC);
CREATE INDEX idx_audit_logs_team_resource ON audit_logs(team_id, resource_type, created_at DESC);
-- sandbox_metrics_snapshots
diff --git a/db/queries/users.sql b/db/queries/users.sql
index 1c902a3..783f22b 100644
--- a/db/queries/users.sql
+++ b/db/queries/users.sql
@@ -4,10 +4,10 @@ VALUES ($1, $2, $3, $4)
RETURNING *;
-- name: GetUserByEmail :one
-SELECT * FROM users WHERE email = $1;
+SELECT * FROM users WHERE email = $1 AND deleted_at IS NULL;
-- name: GetUserByID :one
-SELECT * FROM users WHERE id = $1;
+SELECT * FROM users WHERE id = $1 AND deleted_at IS NULL;
-- name: InsertUserOAuth :one
INSERT INTO users (id, email, name)
diff --git a/envd/internal/services/process/handler/multiplex.go b/envd/internal/services/process/handler/multiplex.go
index a3f6ea3..88f0916 100644
--- a/envd/internal/services/process/handler/multiplex.go
+++ b/envd/internal/services/process/handler/multiplex.go
@@ -35,27 +35,27 @@ func NewMultiplexedChannel[T any](buffer int) *MultiplexedChannel[T] {
c.mu.RUnlock()
}
+ c.mu.Lock()
c.exited.Store(true)
-
for _, cons := range c.channels {
close(cons)
}
+ c.mu.Unlock()
}()
return c
}
func (m *MultiplexedChannel[T]) Fork() (chan T, func()) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
if m.exited.Load() {
ch := make(chan T)
close(ch)
-
return ch, func() {}
}
- m.mu.Lock()
- defer m.mu.Unlock()
-
consumer := make(chan T, 4096)
m.channels = append(m.channels, consumer)
diff --git a/frontend/src/app.css b/frontend/src/app.css
index 823f8ce..d76358b 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -180,9 +180,11 @@ body {
50% { transform: translateY(-6px); }
}
-/* CSS containment — isolate layout/paint for independent UI regions */
+/* CSS containment — isolate paint for independent UI regions.
+ Note: `contain: layout` is omitted because it creates a containing block
+ that breaks `position: fixed` popups rendered inside . */
main {
- contain: layout style;
+ contain: style;
}
/* Respect user motion preferences — covers both CSS class animations and inline style animations */
diff --git a/frontend/src/lib/components/AuthModal.svelte b/frontend/src/lib/components/AuthModal.svelte
deleted file mode 100644
index fddedaa..0000000
--- a/frontend/src/lib/components/AuthModal.svelte
+++ /dev/null
@@ -1,210 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {title}
-
-
- {subtitle}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {switchText}
-
-
-
-
-
-
-
-
diff --git a/frontend/src/lib/components/FilesTab.svelte b/frontend/src/lib/components/FilesTab.svelte
index bdc2a75..46c2d4a 100644
--- a/frontend/src/lib/components/FilesTab.svelte
+++ b/frontend/src/lib/components/FilesTab.svelte
@@ -81,7 +81,7 @@
);
// Breadcrumb segments from currentPath
- const breadcrumbs = $derived(() => {
+ const breadcrumbs = $derived.by(() => {
const parts = currentPath.split('/').filter(Boolean);
const crumbs: { name: string; path: string }[] = [{ name: '/', path: '/' }];
for (let i = 0; i < parts.length; i++) {
@@ -517,7 +517,7 @@
- {#each breadcrumbs() as crumb, i}
+ {#each breadcrumbs as crumb, i}
{#if i > 0}
+
+
{#snippet skeletonRows()}
diff --git a/frontend/src/routes/admin/teams/+page.svelte b/frontend/src/routes/admin/teams/+page.svelte
index a09b3d9..90a8db2 100644
--- a/frontend/src/routes/admin/teams/+page.svelte
+++ b/frontend/src/routes/admin/teams/+page.svelte
@@ -360,7 +360,7 @@
-