fix: mobile layout, task clicks, list tab corners, hashtags in URLs
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m25s

Mobile
- Plus button in triage did nothing: the stored action returned the
  capture function instead of calling it
- Workspace: tabs share the full width; save status sits as an icon next
  to the note title instead of wrapping into its own row; "Neue Notiz" /
  "Neuer Ordner" only in the top menu (and +), not repeated in the list
- Outliner fields measured while hidden stayed one line high (cut-off
  callout); they are measured again when their width changes
- Task dialog: scrolls in a div instead of a fieldset, height in dvh,
  notes editor grows with its text, so long notes are fully reachable

Desktop
- Delete button of the selected task/shopping list sits inside the tab as
  its own rounded button (matches rounded themes like Soft)

Both
- Tasks sometimes did not open: every reload after an action replaced the
  list with a loading placeholder, swallowing clicks; reloads are now
  silent after the first load of a list, stale responses are dropped
- Hashtags need whitespace or "(" before "#", so URL anchors are no
  longer tags (server, browser); one-time cleanup of stored anchor tags

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 16:50:51 +01:00
1 parent 41b02b9564
commit f82ab2fd32
16 files changed
+283 -47

No files matched your search

@@ -0,0 +1,13 @@
import { describe, it, expect } from 'vitest';
import { extractHashtags } from '../hashtags';
describe('extractHashtags', () => {
it('finds tags at the start, after spaces and after "("', () => {
expect(extractHashtags('#start mitten #zwei (#drei)')).toEqual(['start', 'zwei', 'drei']);
});
it('ignores URL anchors and words like C#', () => {
expect(extractHashtags('example.com/blog#ueberschrift')).toEqual([]);
expect(extractHashtags('https://x.de/#anker und #echt, C# nicht')).toEqual(['echt']);
});
});
+2 -1
View File
@@ -28,6 +28,7 @@ import {
UserInvite,
WSMessage,
} from './types';
import { extractHashtags } from './hashtags';
const TOKEN_STORAGE_KEY = 'mindsift_jwt_token';
const OFFLINE_QUEUE_KEY = 'mindsift_offline_capture_queue';
@@ -254,7 +255,7 @@ export const api = {
id: `th_offline_${Date.now()}`,
userId: 'offline',
content,
tags: (content.match(/#([a-zA-Z0-9äöüÄÖÜß_-]+)/g) || []).map((t) => t.slice(1).toLowerCase()),
tags: Array.from(new Set(extractHashtags(content))),
priority,
source,
status: 'inbox',
+9
View File
@@ -0,0 +1,9 @@
// A hashtag starts the text or follows whitespace or "(". Fragments in links (example.com/blog#ueberschrift)
// and words like "C#" are not tags. Same rule as the server (domain.ExtractTags).
const HASHTAG = /(?:^|[\s(])#([a-zA-Z0-9äöüÄÖÜß_-]+)/g;
/** All hashtags in a text, lower case, in order of appearance (with duplicates). */
export function extractHashtags(text: string | undefined | null): string[] {
if (!text) return [];
return Array.from(text.matchAll(HASHTAG), (m) => m[1].toLowerCase());
}