Skip to content

Commit 8797058

Browse files
committed
fix: address 0.8.8 release readiness issues
1 parent d11b334 commit 8797058

26 files changed

Lines changed: 338 additions & 88 deletions

frontend/src/lib/features/collections/CollectionBoard.svelte

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import { buildIterationPickerConfig } from '../iterations/iterationPickerUtils.js';
2121
import ItemTypeIcon from '../../components/ItemTypeIcon.svelte';
2222
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
23-
import { autoScrollWindowForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
23+
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
2424
import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
2525
import ItemDetail from '../items/ItemDetail.svelte';
2626
import PersonalTaskDetail from '../personal/PersonalTaskDetail.svelte';
@@ -87,7 +87,6 @@
8787
let loading = $state(true);
8888
let currentCollectionName = $derived(collectionStore.collectionName);
8989
let setupTimeout;
90-
let autoScrollCleanup;
9190
let setupElements = new Map(); // Track which elements have drag/drop set up and their cleanup functions
9291
let pendingDrops = new Set(); // Track pending drop operations to prevent duplicates
9392
let showItemModal = $state(false);
@@ -145,7 +144,7 @@
145144
// Edge-based drag state
146145
let dragState = $state(new Map()); // Track drag state for each item: { isDragging: boolean, closestEdge: 'top'|'bottom'|null }
147146
let boardAnnouncement = $state('');
148-
let boardViewElement = $state(null);
147+
let boardScrollElement = $state(null);
149148
150149
// Centralized gradient styling
151150
const styles = useGradientStyles();
@@ -178,7 +177,14 @@
178177
179178
onDestroy(() => {
180179
collectionStore.clearBoardSearch();
181-
autoScrollCleanup?.();
180+
});
181+
182+
$effect(() => {
183+
if (!boardScrollElement) return;
184+
return autoScrollForElements({
185+
element: boardScrollElement,
186+
getAllowedAxis: () => 'horizontal',
187+
});
182188
});
183189
184190
// Listen for newly created items
@@ -412,12 +418,6 @@
412418
}
413419
414420
onMount(async () => {
415-
autoScrollCleanup = autoScrollWindowForElements({
416-
canScroll: ({ source }) =>
417-
source.data.type === 'work-item' && Boolean(boardViewElement?.contains(source.element)),
418-
getAllowedAxis: () => 'horizontal',
419-
});
420-
421421
await Promise.all([
422422
workspaceId ? loadWorkspaceGradient(workspaceId) : Promise.resolve(),
423423
workspaceId
@@ -1542,12 +1542,17 @@
15421542
<div class="animate-pulse">{t('common.loading')}</div>
15431543
</div>
15441544
{:else if workspace || !workspaceId}
1545-
<StaticViewBackground
1546-
backgroundStyle={styles.backgroundStyle}
1547-
contextVars={styles.contextVars}
1548-
contentClass="p-6 min-w-fit"
1549-
testid="collection-board-background"
1545+
<div
1546+
bind:this={boardScrollElement}
1547+
class="w-full min-w-0 max-w-full overflow-x-auto"
1548+
data-testid="board-scroll-container"
15501549
>
1550+
<StaticViewBackground
1551+
backgroundStyle={styles.backgroundStyle}
1552+
contextVars={styles.contextVars}
1553+
contentClass="p-6 min-w-fit"
1554+
testid="collection-board-background"
1555+
>
15511556
<!-- Content Container -->
15521557
<!-- Header with view tabs -->
15531558
<div class="mb-8">
@@ -1691,7 +1696,6 @@
16911696
{:else}
16921697
<!-- Board Columns / Swimlanes -->
16931698
<div
1694-
bind:this={boardViewElement}
16951699
class={selectedGroupByItemType ? 'space-y-4' : ''}
16961700
data-testid="board-view"
16971701
>
@@ -1892,7 +1896,8 @@
18921896
</p>
18931897
</div>
18941898
{/if}
1895-
</StaticViewBackground>
1899+
</StaticViewBackground>
1900+
</div>
18961901
{:else}
18971902
<div class="p-6">
18981903
<div class="text-center" style="color: var(--ds-text-subtle);">

frontend/src/lib/features/collections/CollectionMap.svelte

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
import ItemTypeIcon from '../../components/ItemTypeIcon.svelte';
1313
import EmptyState from '../../components/EmptyState.svelte';
1414
import Textarea from '../../components/Textarea.svelte';
15-
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
16-
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
15+
import { draggable, dropTargetForElements, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
16+
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
1717
import Tooltip from '../../components/Tooltip.svelte';
1818
import ViewHeader from '../../layout/ViewHeader.svelte';
1919
import StaticViewBackground from '../../layout/StaticViewBackground.svelte';
@@ -53,6 +53,7 @@
5353
// Item detail modal state
5454
let selectedItemId = $state(null);
5555
let showItemModal = $state(false);
56+
let mapScrollElement = $state(null);
5657
5758
5859
// Centralized gradient styling
@@ -65,6 +66,14 @@
6566
6667
useEventListener(() => window, 'popstate', handlePopState);
6768
69+
$effect(() => {
70+
if (!mapScrollElement) return;
71+
return autoScrollForElements({
72+
element: mapScrollElement,
73+
getAllowedAxis: () => 'horizontal',
74+
});
75+
});
76+
6877
onMount(async () => {
6978
if (workspaceId) {
7079
await loadWorkspaceGradient(workspaceId);
@@ -568,6 +577,7 @@
568577
backgroundStyle={styles.backgroundStyle}
569578
contextVars={styles.contextVars}
570579
contentClass=""
580+
rootStyle="width: 100%; min-width: 0; max-width: 100%;"
571581
testid="map-view"
572582
>
573583
<!-- Header -->
@@ -642,7 +652,11 @@
642652
</div>
643653
644654
<!-- Story Map Container -->
645-
<div class="p-6 overflow-x-auto">
655+
<div
656+
bind:this={mapScrollElement}
657+
class="p-6 overflow-x-auto"
658+
data-testid="map-scroll-container"
659+
>
646660
<div class="min-w-max">
647661
<!-- Backbone (Horizontal) -->
648662
<div

frontend/src/lib/portal/PortalVerifyLink.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
let errorMessage = $state('');
1111
1212
let verificationRun = 0;
13+
let lastVerificationKey = null;
1314
1415
async function verify(currentSlug, currentToken, run) {
1516
if (!currentToken) {
@@ -45,6 +46,11 @@
4546
$effect(() => {
4647
const currentSlug = slug;
4748
const currentToken = token;
49+
const verificationKey = currentSlug && currentToken
50+
? `${currentSlug}:${currentToken}`
51+
: null;
52+
if (verificationKey && verificationKey === lastVerificationKey) return;
53+
lastVerificationKey = verificationKey;
4854
const run = ++verificationRun;
4955
status = 'verifying';
5056
errorMessage = '';

frontend/src/lib/stores/portalAuth.svelte.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,9 @@ function createPortalAuthStore() {
259259
try {
260260
const response = await api.portalAuth.verifyMagicLink(slug, token);
261261
if (response.success) {
262-
customer.set(response.customer);
263-
isAuthenticated.set(true);
262+
// The caller dismisses the one-time-token UI before checkAuth updates
263+
// reactive auth state. Updating it here can remount the verifier and
264+
// redeem the same token a second time.
264265
return { success: true, customer: response.customer };
265266
} else {
266267
error.set(response.message || 'Invalid or expired link');

internal/actionevents/actionevents.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func RunTargets(ctx context.Context, store *TargetStore, eventKey string, callba
288288
}
289289

290290
var targetErrors []error
291-
permanent := false
291+
hasTransientFailure := false
292292
var executed int64
293293
for _, target := range targets {
294294
if target.State == "completed" || target.State == "skipped" {
@@ -298,26 +298,35 @@ func RunTargets(ctx context.Context, store *TargetStore, eventKey string, callba
298298
if completionErr == nil && completed {
299299
if err := store.markCompleted(ctx, eventKey, target.ActionID); err != nil {
300300
targetErrors = append(targetErrors, err)
301+
hasTransientFailure = true
301302
}
302303
continue
303304
}
304305
if completionErr != nil && !errors.Is(completionErr, sql.ErrNoRows) {
305306
targetErrors = append(targetErrors, fmt.Errorf("load durable action execution: %w", completionErr))
307+
hasTransientFailure = true
306308
continue
307309
}
308310
if err := store.markRunning(ctx, eventKey, target.ActionID); err != nil {
309311
targetErrors = append(targetErrors, err)
312+
hasTransientFailure = true
310313
continue
311314
}
312315
isPermanent, err := callbacks.Execute(target.ActionID)
313316
if err != nil {
314-
permanent = permanent || isPermanent
315-
_ = store.markFailed(ctx, eventKey, target.ActionID, err)
317+
if !isPermanent {
318+
hasTransientFailure = true
319+
}
320+
if markErr := store.markFailed(ctx, eventKey, target.ActionID, err); markErr != nil {
321+
targetErrors = append(targetErrors, markErr)
322+
hasTransientFailure = true
323+
}
316324
targetErrors = append(targetErrors, err)
317325
continue
318326
}
319327
if err := store.markCompleted(ctx, eventKey, target.ActionID); err != nil {
320328
targetErrors = append(targetErrors, err)
329+
hasTransientFailure = true
321330
continue
322331
}
323332
executed++
@@ -326,8 +335,8 @@ func RunTargets(ctx context.Context, store *TargetStore, eventKey string, callba
326335
return executed, nil
327336
}
328337
err = errors.Join(targetErrors...)
329-
if permanent {
330-
return executed, events.Permanent(err)
338+
if hasTransientFailure {
339+
return executed, err
331340
}
332-
return executed, err
341+
return executed, events.Permanent(err)
333342
}

internal/database/migrations.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,14 @@ var Catalog = []Migration{
499499
Postgres: `ALTER TABLE sso_providers ALTER COLUMN attribute_mapping SET DEFAULT '{"email":"email","name":"name","given_name":"given_name","family_name":"family_name","username":"preferred_username","email_verified":"email_verified"}'`,
500500
ApplySQLite: applySQLiteSSOAttributeMappingDefault,
501501
},
502+
{
503+
Version: "20260901_daily_briefing_workspace_provenance",
504+
Name: "Record daily briefing workspace provenance",
505+
CheckSQLite: sqliteColumnCheck("daily_briefings", "source_workspace_ids"),
506+
CheckPostgres: pgColumnCheck("daily_briefings", "source_workspace_ids"),
507+
SQLite: "ALTER TABLE daily_briefings ADD COLUMN source_workspace_ids TEXT",
508+
Postgres: "ALTER TABLE daily_briefings ADD COLUMN source_workspace_ids TEXT",
509+
},
502510
}
503511

504512
func applySQLiteSSOAttributeMappingDefault(db Database) (retErr error) {

internal/database/schema/daily_briefings.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS daily_briefings (
33
user_id INTEGER NOT NULL,
44
date TEXT NOT NULL,
55
content TEXT NOT NULL,
6+
source_workspace_ids TEXT,
67
generation_duration_ms INTEGER,
78
error TEXT,
89
lock_until DATETIME,

internal/database/schema/daily_briefings_postgres.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS daily_briefings (
33
user_id INTEGER NOT NULL,
44
date TEXT NOT NULL,
55
content TEXT NOT NULL,
6+
source_workspace_ids TEXT,
67
generation_duration_ms INTEGER,
78
error TEXT,
89
lock_until TIMESTAMPTZ,

internal/events/engine.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ type Engine struct {
9797

9898
mu sync.Mutex
9999
handlers map[string]Handler
100+
nextHandler int
100101
started bool
101102
used bool
102103
cancel context.CancelFunc
@@ -349,7 +350,7 @@ func (e *Engine) handle(ctx context.Context, handler Handler, delivery Delivery)
349350
return nil
350351
}
351352

352-
retry := !isPermanent(err) && delivery.AttemptCount < e.config.MaxAttempts
353+
retry := !IsPermanent(err) && delivery.AttemptCount < e.config.MaxAttempts
353354
nextAttempt := e.now()
354355
if retry {
355356
nextAttempt = nextAttempt.Add(e.retryDelay(delivery.AttemptCount))
@@ -381,7 +382,15 @@ func (e *Engine) handlerKeys() []string {
381382
keys = append(keys, key)
382383
}
383384
slices.Sort(keys)
384-
return keys
385+
if len(keys) == 0 {
386+
return keys
387+
}
388+
start := e.nextHandler % len(keys)
389+
e.nextHandler = (start + 1) % len(keys)
390+
if start == 0 {
391+
return keys
392+
}
393+
return append(keys[start:], keys[:start]...)
385394
}
386395

387396
func (e *Engine) handler(key string) Handler {

internal/events/event.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ func Permanent(err error) error {
207207
return permanentFailure{err: err}
208208
}
209209

210-
func isPermanent(err error) bool {
210+
// IsPermanent reports whether an error disables automatic delivery retries.
211+
func IsPermanent(err error) bool {
211212
var target permanentFailure
212213
return errors.As(err, &target)
213214
}

0 commit comments

Comments
 (0)