mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-11 19:23:03 +02:00
+8
-24
@@ -207,36 +207,20 @@ export function App() {
|
|||||||
|
|
||||||
const handleToggleFavorite = useCallback(
|
const handleToggleFavorite = useCallback(
|
||||||
async (type: 'channel' | 'contact', id: string) => {
|
async (type: 'channel' | 'contact', id: string) => {
|
||||||
const ts = performance.now().toFixed(2);
|
|
||||||
console.log(
|
|
||||||
`[fav-debug] handleToggleFavorite called t=${ts} type=${type} id=${id.slice(0, 12)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Optimistically toggle the favorite flag
|
// Optimistically toggle the favorite flag
|
||||||
if (type === 'contact') {
|
if (type === 'contact') {
|
||||||
setContacts((prev) => {
|
setContacts((prev) =>
|
||||||
const match = prev.find((c) => c.public_key === id);
|
prev.map((c) => (c.public_key === id ? { ...c, favorite: !c.favorite } : c))
|
||||||
console.log(
|
);
|
||||||
`[fav-debug] optimistic contact toggle t=${ts} current=${match?.favorite} → ${!match?.favorite}`
|
|
||||||
);
|
|
||||||
return prev.map((c) => (c.public_key === id ? { ...c, favorite: !c.favorite } : c));
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setChannels((prev) => {
|
setChannels((prev) =>
|
||||||
const match = prev.find((c) => c.key === id);
|
prev.map((c) => (c.key === id ? { ...c, favorite: !c.favorite } : c))
|
||||||
console.log(
|
);
|
||||||
`[fav-debug] optimistic channel toggle t=${ts} current=${match?.favorite} → ${!match?.favorite}`
|
|
||||||
);
|
|
||||||
return prev.map((c) => (c.key === id ? { ...c, favorite: !c.favorite } : c));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[fav-debug] firing API request t=${ts}`);
|
await api.toggleFavorite(type, id);
|
||||||
const result = await api.toggleFavorite(type, id);
|
} catch {
|
||||||
console.log(`[fav-debug] API response t=${ts}`, result);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[fav-debug] API error t=${ts}`, err);
|
|
||||||
// Revert on failure
|
// Revert on failure
|
||||||
if (type === 'contact') {
|
if (type === 'contact') {
|
||||||
setContacts((prev) =>
|
setContacts((prev) =>
|
||||||
|
|||||||
@@ -120,12 +120,7 @@ export function ChannelInfoPane({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-sm flex items-center gap-2 hover:text-primary transition-colors"
|
className="text-sm flex items-center gap-2 hover:text-primary transition-colors"
|
||||||
onClick={(e) => {
|
onClick={() => onToggleFavorite('channel', channel.key)}
|
||||||
console.log(
|
|
||||||
`[fav-debug] ChannelInfoPane star clicked t=${performance.now().toFixed(2)} detail=${e.detail} isTrusted=${e.isTrusted}`
|
|
||||||
);
|
|
||||||
onToggleFavorite('channel', channel.key);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{channel.favorite ? (
|
{channel.favorite ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -351,12 +351,9 @@ export function ChatHeader({
|
|||||||
{(conversation.type === 'channel' || conversation.type === 'contact') && (
|
{(conversation.type === 'channel' || conversation.type === 'contact') && (
|
||||||
<button
|
<button
|
||||||
className="p-1 rounded hover:bg-accent text-lg leading-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="p-1 rounded hover:bg-accent text-lg leading-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
onClick={(e) => {
|
onClick={() =>
|
||||||
console.log(
|
onToggleFavorite(conversation.type as 'channel' | 'contact', conversation.id)
|
||||||
`[fav-debug] star clicked t=${performance.now().toFixed(2)} detail=${e.detail} isTrusted=${e.isTrusted}`
|
}
|
||||||
);
|
|
||||||
onToggleFavorite(conversation.type as 'channel' | 'contact', conversation.id);
|
|
||||||
}}
|
|
||||||
title={favoriteTitle}
|
title={favoriteTitle}
|
||||||
aria-label={isFav ? 'Remove from favorites' : 'Add to favorites'}
|
aria-label={isFav ? 'Remove from favorites' : 'Add to favorites'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -377,12 +377,7 @@ export function ContactInfoPane({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-sm flex items-center gap-2 hover:text-primary transition-colors"
|
className="text-sm flex items-center gap-2 hover:text-primary transition-colors"
|
||||||
onClick={(e) => {
|
onClick={() => onToggleFavorite('contact', contact.public_key)}
|
||||||
console.log(
|
|
||||||
`[fav-debug] ContactInfoPane star clicked t=${performance.now().toFixed(2)} detail=${e.detail} isTrusted=${e.isTrusted}`
|
|
||||||
);
|
|
||||||
onToggleFavorite('contact', contact.public_key);
|
|
||||||
}}
|
|
||||||
title="Favorite contacts stay loaded on the radio for ACK support"
|
title="Favorite contacts stay loaded on the radio for ACK support"
|
||||||
>
|
>
|
||||||
{contact.favorite ? (
|
{contact.favorite ? (
|
||||||
|
|||||||
@@ -266,12 +266,7 @@ export function RepeaterDashboard({
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="p-1 rounded hover:bg-accent text-lg leading-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="p-1 rounded hover:bg-accent text-lg leading-none transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
onClick={(e) => {
|
onClick={() => onToggleFavorite('contact', conversation.id)}
|
||||||
console.log(
|
|
||||||
`[fav-debug] RepeaterDashboard star clicked t=${performance.now().toFixed(2)} detail=${e.detail} isTrusted=${e.isTrusted}`
|
|
||||||
);
|
|
||||||
onToggleFavorite('contact', conversation.id);
|
|
||||||
}}
|
|
||||||
title={
|
title={
|
||||||
isFav
|
isFav
|
||||||
? 'Remove from favorites. Favorite contacts stay loaded on the radio for ACK support.'
|
? 'Remove from favorites. Favorite contacts stay loaded on the radio for ACK support.'
|
||||||
|
|||||||
@@ -12,16 +12,7 @@ export function mergeContactIntoList(contacts: Contact[], incoming: Contact): Co
|
|||||||
const idx = contacts.findIndex((c) => c.public_key === incoming.public_key);
|
const idx = contacts.findIndex((c) => c.public_key === incoming.public_key);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const existing = contacts[idx];
|
const existing = contacts[idx];
|
||||||
// Preserve user-action-only fields that should not be overwritten by
|
const merged = { ...existing, ...incoming };
|
||||||
// radio-event-driven WS updates (adverts, path updates, syncs). These
|
|
||||||
// fields are only changed via explicit user actions (favorite toggle,
|
|
||||||
// mark-read) or full REST refetches, not via mesh/radio events.
|
|
||||||
const merged = {
|
|
||||||
...existing,
|
|
||||||
...incoming,
|
|
||||||
favorite: existing.favorite,
|
|
||||||
last_read_at: existing.last_read_at,
|
|
||||||
};
|
|
||||||
const unchanged = (Object.keys(merged) as (keyof Contact)[]).every(
|
const unchanged = (Object.keys(merged) as (keyof Contact)[]).every(
|
||||||
(k) => existing[k] === merged[k]
|
(k) => existing[k] === merged[k]
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user