Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions web/e2e/task-3589-mobile-saved-views.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { test, expect } from './fixtures';
import { browserLogin } from './lib/collab-helpers';
import { authJson } from './lib/attachment-viewer';

/**
* TASK-3589: a saved view can be selected on a phone. The mobile "Choose view"
* sheet listed only List, Board and Table; saved views and "All" lived only in
* the desktop menu, so a saved workflow vanished at phone width. Selecting is
* the scope here; managing views stays on the desktop.
*/
test('the mobile view sheet lists saved views; picking one applies its filters, "All" clears them', async ({ page, fixture, request }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile-chromium', 'the mobile sheet');
// A word the list search matches (a bare number is not tokenized as one).
const stamp = `zqv${Date.now().toString(36)}`;
const ws = fixture.workspaceSlug;
const mk = async (title: string, priority: string) => {
const r = await request.post(`/api/v1/workspaces/${ws}/collections/tasks/items`, {
headers: authJson(fixture),
data: { title, fields: JSON.stringify({ status: 'open', priority }) },
});
expect(r.ok(), await r.text()).toBeTruthy();
};
await mk(`B3589 high ${stamp}`, 'high');
await mk(`B3589 low ${stamp}`, 'low');
const viewName = `Hot ${stamp}`;
const v = await request.post(`/api/v1/workspaces/${ws}/collections/tasks/views`, {
headers: authJson(fixture),
data: { name: viewName, view_type: 'list', config: JSON.stringify({ filters: [{ field: 'priority', op: 'eq', value: 'high' }] }) },
});
expect(v.ok(), await v.text()).toBeTruthy();

await browserLogin(page);
await page.goto(`/${fixture.adminUsername}/${ws}/tasks?view=list&q=${stamp}`);
await expect(page.getByText(`B3589 low ${stamp}`)).toBeVisible({ timeout: 15_000 });

await page.locator('button.view-chip').click();
const sheet = page.getByRole('dialog', { name: 'Choose view' });
await expect(sheet.getByText('Saved views')).toBeVisible();
await sheet.getByRole('button', { name: viewName }).click();
await expect(sheet).toBeHidden();
await expect(page.getByText(`B3589 low ${stamp}`)).toHaveCount(0);
await expect(page.getByText(`B3589 high ${stamp}`)).toBeVisible();
await expect(page).toHaveURL(/priority=high/);

// "All" clears the view's filters again.
await page.locator('button.view-chip').click();
await page.getByRole('dialog', { name: 'Choose view' }).getByRole('button', { name: 'All', exact: true }).click();
await expect(page.getByText(`B3589 low ${stamp}`)).toBeVisible();
});
1 change: 1 addition & 0 deletions web/src/lib/eachKeyCensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export const EACH_KEY_CENSUS: Record<string, { reason: EachKeyReason; note: stri
"routes/[username]/[workspace]/[collection]/+page.svelte :: each VIEW_CHOICES as choice (choice.mode)": { reason: 'static-literal', note: "three distinct modes" },
"routes/[username]/[workspace]/[collection]/+page.svelte :: each blockedDraftNotices as notice (notice.lane)": { reason: 'object-or-map-keys', note: "`blockedDraftNotices` from `Object.entries(draftPlacement)` (`draftTargets`, laneDrafts.ts:219) | object keys are unique" },
"routes/[username]/[workspace]/[collection]/+page.svelte :: each savedViews as view (view.id)": { reason: 'db-primary-key', note: "views PK; the create append checks the id first" },
"routes/[username]/[workspace]/[collection]/+page.svelte :: each savedViews as view (view.id) #2": { reason: 'db-primary-key', note: "views PK; the create append checks the id first. Same list in the desktop menu; #1 is the mobile view sheet (TASK-3589)" },
"routes/[username]/[workspace]/[collection]/+page.svelte :: each sortOptions as opt (opt.value)": { reason: 'static-literal', note: "`SORT_OPTIONS` (lib/collections/itemSort.ts:12), optionally filtered | static literal with five distinct values" },
"routes/[username]/[workspace]/activity/+page.svelte :: each ACTOR_OPTIONS as opt (opt.value)": { reason: 'static-literal', note: "static `ACTOR_OPTIONS` (lib/activity/activityFilters.ts:19) | Literal list with three distinct values: '', 'user', 'agent'." },
"routes/[username]/[workspace]/activity/+page.svelte :: each collections as coll (coll.id)": { reason: 'db-primary-key', note: "`api.collections.list` \u2192 `store.ListCollections` | DB primary key from a single SELECT of live collections." },
Expand Down
42 changes: 42 additions & 0 deletions web/src/routes/[username]/[workspace]/[collection]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4061,6 +4061,39 @@
<span class="view-sheet-icon">&#9783;</span>
<span>Table</span>
</button>
<!--
Saved views (TASK-3589): selecting one works on a phone as
on the desktop menu. Managing them (save, delete, make
default) stays on the desktop for now.
-->
{#if savedViews.length > 0}
<div class="view-sheet-label" id="view-sheet-saved">Saved views</div>
<button
class="view-sheet-option"
class:active={activeViewId === null}
type="button"
aria-describedby="view-sheet-saved"
onclick={() => { clearActiveView(); viewSheetOpen = false; }}
>
<span class="view-sheet-icon" aria-hidden="true">&#9733;</span>
<span>All</span>
</button>
{#each savedViews as view (view.id)}
<button
class="view-sheet-option"
class:active={activeViewId === view.id}
type="button"
aria-describedby="view-sheet-saved"
onclick={() => { applyViewConfig(view); viewSheetOpen = false; }}
>
<span class="view-sheet-icon" aria-hidden="true">&#9733;</span>
<span>{view.name}</span>
{#if defaultViewId === view.id}
<span class="saved-view-default" title="Default view — applied on entry" aria-label="Default view">📌</span>
{/if}
</button>
{/each}
{/if}
</div>
</BottomSheet>
{/if}
Expand Down Expand Up @@ -5088,6 +5121,15 @@
font-weight: 600;
}

/* TASK-3589: the saved-views group in the mobile view sheet. */
.view-sheet-label {
padding: var(--space-3) var(--space-3) var(--space-1);
font-size: 0.8em;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
}

.view-sheet-icon {
font-size: 1.1em;
width: 1.5em;
Expand Down
Loading