= (props) => {
return getThemeAdjustedColor(def?.color, def?.theme, resolvedTheme()) || "inherit";
})()
}}
- onClick={() => setTags(tags().filter(t => t !== tag))}
+ onClick={() => {
+ setTags(tags().filter(t => t !== tag));
+ setTagShareModes(prev => {
+ const next = { ...prev };
+ delete next[tag.toLowerCase()];
+ return next;
+ });
+ }}
>
void;
+ onTagsChange: (tags: string[], shareModeOverrides?: Record) => void;
+ getShareModeForTag?: (tag: string) => "handoff" | "collaborative" | undefined;
}
export const TagPicker: Component = (props) => {
@@ -75,6 +76,11 @@ export const TagPicker: Component = (props) => {
});
createEffect(() => {
+ const explicitMode = props.getShareModeForTag?.(selectedTagName().trim());
+ if (explicitMode) {
+ setShareMode(explicitMode);
+ return;
+ }
const context = selectedShareContext();
if (context) {
setShareMode(context.policy);
@@ -94,9 +100,6 @@ export const TagPicker: Component = (props) => {
const name = selectedTagName().trim();
if (!name) return;
- const context = selectedShareContext();
- const noteContext = selectedNoteContext();
-
if (name.startsWith("@")) {
const normalized = normalizeContextKey(name);
const exists = store.contexts.some(context =>
@@ -109,11 +112,6 @@ export const TagPicker: Component = (props) => {
toast.error("Create the shared context first before tagging tasks with it.");
return;
}
-
- if (context && context.policy !== shareMode()) {
- const updated = await setContextPolicy(context.id, shareMode());
- if (!updated) return;
- }
}
if (name.startsWith("#")) {
@@ -122,11 +120,6 @@ export const TagPicker: Component = (props) => {
toast.error("Create the canonical note first before linking it with a #tag.");
return;
}
-
- if (noteContext && getNoteSharePolicy(noteContext) !== shareMode()) {
- const updated = await setNoteContextPolicy(noteContext.id, shareMode());
- if (!updated) return;
- }
}
// update global definition
@@ -138,9 +131,13 @@ export const TagPicker: Component = (props) => {
// Toggle in current task selection if not already there
const currentSelected = props.selectedTags;
- if (!currentSelected.includes(name)) {
- props.onTagsChange([...currentSelected, name]);
- }
+ const nextTags = currentSelected.includes(name) ? currentSelected : [...currentSelected, name];
+ props.onTagsChange(
+ nextTags,
+ name.startsWith("@") || name.startsWith("#")
+ ? { [name.toLowerCase()]: shareMode() }
+ : undefined
+ );
// Cleanup
setSelectedTagName("");
diff --git a/src/components/TaskCard.tsx b/src/components/TaskCard.tsx
index 213d450..ee64dce 100644
--- a/src/components/TaskCard.tsx
+++ b/src/components/TaskCard.tsx
@@ -1,5 +1,5 @@
import { type Component, createMemo, For, Show } from "solid-js";
-import { type Task, calculateUrgencyFromDate, setActiveTaskId, store, copyTask, updateTask, currentTaskContext, now, getFavoriteTag, isCurrentUserContextTag, dismissTaskUpdateIndicator, isTaskUpdatedByAnotherUser, collapseTaskUntilUpdated, expandCollapsedTask, isTaskCollapsedUntilUpdated, isTaskCollaborativelyShared } from "@/store";
+import { type Task, calculateUrgencyFromDate, setActiveTaskId, store, copyTask, updateTask, currentTaskContext, now, getFavoriteTag, dismissTaskUpdateIndicator, isTaskUpdatedByAnotherUser, collapseTaskUntilUpdated, expandCollapsedTask, isTaskCollapsedUntilUpdated, isTaskCollaborativelyShared, getVisibleTaskTags } from "@/store";
import { cn } from "@/lib/utils";
import { Clock, ArrowUpCircle, Copy, Users2, Star, ChevronRight } from "lucide-solid";
import { Badge } from "@/components/ui/badge";
@@ -8,7 +8,6 @@ import { getThemeAdjustedColor } from "@/lib/colors";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { getStatusOptionsForTask } from "@/lib/constants";
import { getRecurrenceProgress } from "@/lib/recurrence";
-import { buildShareTag } from "@/lib/tags";
import { StatusCircle } from "./StatusCircle";
export const TaskCard: Component<{ task: Task, isShaking?: boolean }> = (props) => {
@@ -28,18 +27,8 @@ export const TaskCard: Component<{ task: Task, isShaking?: boolean }> = (props)
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
};
- // Filter out bucket tags if we are currently INSIDE that bucket view
const visibleTags = createMemo(() => {
- const tags = props.task.tags || [];
- const ctx = currentTaskContext();
-
- if (typeof ctx === 'object' && 'bucketId' in ctx) {
- const bucketContext = store.contexts.find(context => context.id === ctx.bucketId);
- const bucketTagName = buildShareTag(bucketContext?.displayName || ctx.name).toLowerCase();
- return tags.filter(t => t.toLowerCase() !== bucketTagName && !t.endsWith("_favorite__") && !isCurrentUserContextTag(t));
- }
-
- return tags.filter(t => !t.endsWith("_favorite__") && !isCurrentUserContextTag(t));
+ return getVisibleTaskTags(props.task, currentTaskContext());
});
const isDueWithin12Hours = createMemo(() => {
@@ -187,13 +176,13 @@ export const TaskCard: Component<{ task: Task, isShaking?: boolean }> = (props)
P{props.task.priority}
- {/* Shared Indicator - driven by @tag share contexts */}
+ {/* Shared Indicator - driven by task context tags */}
{(() => {
if (props.task.shareRefs.length === 0) return null;
const tooltip = props.task.shareRefs.length === 1
- ? 'Shared via 1 @context'
- : `Shared via ${props.task.shareRefs.length} @contexts`;
+ ? 'Shared via 1 context'
+ : `Shared via ${props.task.shareRefs.length} contexts`;
return (
diff --git a/src/components/TaskDetail.tsx b/src/components/TaskDetail.tsx
index f81f04d..f371ca1 100644
--- a/src/components/TaskDetail.tsx
+++ b/src/components/TaskDetail.tsx
@@ -1,6 +1,6 @@
import { type Component, createEffect, createSignal, For, createMemo, onCleanup, Show } from "solid-js";
import { Sheet, SheetContent } from "@/components/ui/sheet";
-import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate, loadTaskContent, currentTaskContext, cleanupTaskAttachments, getFavoriteTag, now, isCurrentUserContextTag } from "@/store";
+import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate, loadTaskContent, currentTaskContext, cleanupTaskAttachments, getFavoriteTag, now, isCurrentUserContextTag, getVisibleTaskTags, isTaskLockedNoteTag, deriveTaskRelationsFromTags, getTaskShareModeForTag, getTaskSharePolicy } from "@/store";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X, Copy, MoreHorizontal, Gauge, Star, FileText } from "lucide-solid";
@@ -17,7 +17,7 @@ import { lazy, Suspense } from "solid-js";
import { useTheme } from "./ThemeProvider";
import { getThemeAdjustedColor } from "@/lib/colors";
import { getRecurrenceProgress } from "@/lib/recurrence";
-import { buildShareTag } from "@/lib/tags";
+import { buildNoteTag, buildShareTag } from "@/lib/tags";
import { TextBubbleMenu } from "@/components/TextBubbleMenu";
import { TableBubbleMenu } from "@/components/TableBubbleMenu";
@@ -168,7 +168,7 @@ export const TaskDetail: Component
= (props) => {
}
};
- const updateTags = (tags: string[]) => {
+ const updateTags = (tags: string[], shareModeOverrides?: Record) => {
const ctx = currentTaskContext();
const currentTags = props.task.tags || [];
let newTags = [...tags];
@@ -191,7 +191,16 @@ export const TaskDetail: Component = (props) => {
}
}
- updateTask(props.task.id, { tags: newTags });
+ props.task.tags
+ .filter(tag => isTaskLockedNoteTag(props.task, tag))
+ .forEach(tag => {
+ if (!newTags.some(existing => existing.toLowerCase() === tag.toLowerCase())) {
+ newTags.push(tag);
+ }
+ });
+
+ const { labelTags, shareRefs, noteRefs } = deriveTaskRelationsFromTags(newTags, props.task.shareRefs, shareModeOverrides);
+ updateTask(props.task.id, { tags: newTags, labelTags, shareRefs, noteRefs });
};
const onDateInputChange = (e: Event) => {
@@ -207,16 +216,7 @@ export const TaskDetail: Component = (props) => {
// Filter out bucket tags if we are currently INSIDE that bucket view
const visibleTags = createMemo(() => {
- const tags = props.task.tags || [];
- const ctx = currentTaskContext();
-
- if (typeof ctx === 'object' && 'bucketId' in ctx) {
- const bucketContext = store.contexts.find(context => context.id === ctx.bucketId);
- const bucketTagName = buildShareTag(bucketContext?.displayName || ctx.name).toLowerCase();
- return tags.filter(t => t.toLowerCase() !== bucketTagName && !t.endsWith("_favorite__") && !isCurrentUserContextTag(t));
- }
-
- return tags.filter(t => !t.endsWith("_favorite__") && !isCurrentUserContextTag(t));
+ return getVisibleTaskTags(props.task, currentTaskContext());
});
const statusOptions = createMemo(() => getStatusOptionsForTask(props.task.recurrence));
@@ -511,6 +511,7 @@ export const TaskDetail: Component = (props) => {