fix(workspace): hidden bullet actions on phones, no second extract, notice layout
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m20s

- The floating bullet actions only react to hover on devices with a real
  pointer; a tap on a phone made the invisible buttons clickable.
- A bullet that is only a note link (plus hashtags) can no longer be
  extracted: button and /extract are hidden, the server answers 400.
- Extract notice on phones sits above the bottom navigation, text on
  top (two lines max) with the actions below.
- Release v0.26.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-31 19:56:32 +01:00
1 parent fdd6e38065
commit 68bc4d279c
12 files changed
+78 -27

No files matched your search

+1 -1
View File
@@ -29,7 +29,7 @@ func restructureStatus(err error) int {
return http.StatusConflict
case errors.Is(err, storage.ErrBlockNotFound), errors.Is(err, storage.ErrDocNotFound):
return http.StatusNotFound
case errors.Is(err, storage.ErrMergeSelf), errors.Is(err, storage.ErrDocInTrash):
case errors.Is(err, storage.ErrMergeSelf), errors.Is(err, storage.ErrDocInTrash), errors.Is(err, storage.ErrAlreadyNote):
return http.StatusBadRequest
case errors.Is(err, storage.ErrMergeNotOwner):
return http.StatusForbidden
+17
View File
@@ -84,6 +84,23 @@ func TestExtractBranchIntoOwnNote(t *testing.T) {
}
}
func TestExtractRejectsBulletThatIsOnlyANoteLink(t *testing.T) {
srv, store, token := setupTestServer(t)
router := srv.Router()
claims, _ := auth.ValidateToken(testJWTSecret, token)
src, _ := store.CreateDocument(claims.UserID, "", "Projekt", "", "", []domain.DocumentBlock{
{ID: "blk_l", Type: "bullet", Content: "[[Konzept: Garten]] #draußen", Level: 0, Position: 1},
{ID: "blk_m", Type: "bullet", Content: "Siehe [[Konzept: Garten]] für Details", Level: 0, Position: 2},
}, nil, false)
if rec := sessionCall(router, "POST", "/api/v1/documents/"+src.ID+"/extract", token, `{"blockId":"blk_l"}`, ""); rec.Code != http.StatusBadRequest {
t.Fatalf("link-only bullet: want 400, got %d %s", rec.Code, rec.Body.String())
}
// A link inside other text is ordinary content
if rec := sessionCall(router, "POST", "/api/v1/documents/"+src.ID+"/extract", token, `{"blockId":"blk_m"}`, ""); rec.Code != http.StatusCreated {
t.Fatalf("bullet with text and link: want 201, got %d %s", rec.Code, rec.Body.String())
}
}
func TestExtractInSharedNoteKeepsAccess(t *testing.T) {
srv, store, _ := setupTestServer(t)
router := srv.Router()
+6
View File
@@ -20,6 +20,7 @@ var (
ErrBlockNotFound = errors.New("Punkt nicht gefunden")
ErrBranchLocked = errors.New("Jemand anderes bearbeitet gerade einen Punkt in diesem Ast. Bitte kurz warten.")
ErrMergeSelf = errors.New("Eine Notiz kann nicht mit sich selbst zusammengeführt werden")
ErrAlreadyNote = errors.New("Dieser Punkt verweist schon auf eine eigene Notiz")
ErrMergeNotOwner = errors.New("Zusammenführen geht nur mit eigenen Notizen als Quelle")
)
@@ -37,6 +38,8 @@ func (s *Store) readCollabDoc(docID string) (*collab.Doc, error) {
var (
titlePrefixRe = regexp.MustCompile(`^(\s*(-|\*|#{1,6}|\[[ xX]?\])\s+)+`)
titleTagRe = regexp.MustCompile(`(^|\s)#[\p{L}0-9_-]+`)
// A bullet that is only a link to a note (with hashtags), as left behind by ExtractBranch
noteLinkOnlyRe = regexp.MustCompile(`^\s*\[\[[^\]]+\]\](\s+#[\p{L}0-9_-]+)*\s*$`)
)
// branchTitle makes a note title from a bullet's text (without hashtags) and returns the hashtags,
@@ -116,6 +119,9 @@ func (s *Store) ExtractBranch(userID, docID, blockID string, a collab.Actor, lea
return nil, "", ErrBlockNotFound
}
root := flat[start]
if noteLinkOnlyRe.MatchString(root.Content) {
return nil, "", ErrAlreadyNote
}
var children []domain.DocumentBlock
ids := []string{root.ID}
for i := start + 1; i < len(flat) && flat[i].Level > root.Level; i++ {