fixed issues namely reveloved around chat

This commit is contained in:
JCEEE
2026-08-25 10:54:18 +01:00
parent f63e2918ff
commit e904bb8210
5 changed files with 96 additions and 38 deletions
+15 -6
View File
@@ -14,6 +14,14 @@
const isOwn = (msg: any) => msg.authorId === chatStore.actorId; const isOwn = (msg: any) => msg.authorId === chatStore.actorId;
// Colour comes from the sender's user record (loaded with the family on
// page load) — no per-message snapshot, no live colour tracking. Falls back
// to the stored authorColor for any message sent before this change.
function colorOf(msg: any): string {
if (msg.authorId === chatStore.actorId) return chatStore.actorColor || '#6366f1';
return msg.authorColor || '#6366f1';
}
function scrollToBottom(instant = false) { function scrollToBottom(instant = false) {
if (!container) return; if (!container) return;
container.scrollTo({ container.scrollTo({
@@ -107,15 +115,18 @@
{#each chatStore.messages as msg (msg.id)} {#each chatStore.messages as msg (msg.id)}
<div class="msg-row" class:own={isOwn(msg)}> <div class="msg-row" class:own={isOwn(msg)}>
{#if !isOwn(msg)} {#if !isOwn(msg)}
<span class="avatar" style="background:{msg.authorColor || '#6366f1'}"> <span class="avatar" style="background:{colorOf(msg)}">
{msg.authorName?.charAt(0).toUpperCase()} {msg.authorName?.charAt(0).toUpperCase()}
</span> </span>
{/if} {/if}
<div class="bubble-wrap"> <div class="bubble-wrap">
{#if !isOwn(msg)} {#if !isOwn(msg)}
<span class="msg-author">{msg.authorName}</span> <span class="msg-author" style="color:{colorOf(msg)}">{msg.authorName}</span>
{/if} {/if}
<div class="bubble"> <div
class="bubble"
style="background:{colorOf(msg) + '33'}; color:{colorOf(msg)}"
>
{#each renderContent(msg.content) as seg (msg.id + ':' + seg.text)} {#each renderContent(msg.content) as seg (msg.id + ':' + seg.text)}
{#if seg.mention} {#if seg.mention}
<button class="mention" onclick={() => insertMention(seg.text.slice(1))} <button class="mention" onclick={() => insertMention(seg.text.slice(1))}
@@ -274,8 +285,6 @@
position: relative; position: relative;
} }
.msg-row.own .bubble { .msg-row.own .bubble {
background: #6366f1;
color: #fff;
border-bottom-left-radius: 14px; border-bottom-left-radius: 14px;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
} }
@@ -288,7 +297,7 @@
text-transform: capitalize; text-transform: capitalize;
} }
.msg-row.own .msg-time { .msg-row.own .msg-time {
color: rgba(255, 255, 255, 0.7); color: rgba(17, 24, 39, 0.45);
} }
.mention { .mention {
background: #ede9fe; background: #ede9fe;
+23 -20
View File
@@ -106,27 +106,30 @@ async function ensureUsers(ids: Record<string, string>): Promise<void> {
changed = true; changed = true;
} }
const listRule = "famId = @request.auth.famId"; const listRule = "famId = @request.auth.famId";
const parentWrite = "famId = @request.auth.famId && @request.auth.role = 'parent'"; const parentWrite = "famId = @request.auth.famId && @request.auth.role = 'parent'";
if (usersCol.listRule !== listRule || usersCol.viewRule !== listRule || // Members can edit their own record (name/colour); parents can edit any
usersCol.updateRule !== parentWrite || usersCol.deleteRule !== parentWrite) { // family member. Delete stays parent-only.
changed = true; const selfOrParentWrite = "@request.auth.id = id || (famId = @request.auth.famId && @request.auth.role = 'parent')";
} if (usersCol.listRule !== listRule || usersCol.viewRule !== listRule ||
usersCol.updateRule !== selfOrParentWrite || usersCol.deleteRule !== parentWrite) {
changed = true;
}
if (changed) { if (changed) {
await updateCollection(usersCol.id, { await updateCollection(usersCol.id, {
name: "users", name: "users",
type: "auth", type: "auth",
listRule, listRule,
viewRule: listRule, viewRule: listRule,
createRule: usersCol.createRule || "", createRule: usersCol.createRule || "",
updateRule: parentWrite, updateRule: selfOrParentWrite,
deleteRule: parentWrite, deleteRule: parentWrite,
fields: usersCol.fields, fields: usersCol.fields,
indexes, indexes,
passwordAuth: { enabled: true, identityFields }, passwordAuth: { enabled: true, identityFields },
}); });
} }
} }
// Superuser-only OTP store for the child join gate. Holds the rotating code and // Superuser-only OTP store for the child join gate. Holds the rotating code and
+26 -9
View File
@@ -50,21 +50,38 @@ class ChatStore {
}); });
async init(opts: ChatInit) { async init(opts: ChatInit) {
// Re-initialize whenever the family OR the actor changes (e.g. switching
// between parent and child in the same browser session). The store is a
// module singleton — keeping the previous actor's identity/subscription
// would make one direction's realtime silently dead.
const sameIdentity =
this.initialized &&
this.famId === opts.famId &&
this.actorId === opts.actorId &&
this.actorType === opts.actorType;
if (sameIdentity) return;
if (this.initPromise) {
await this.initPromise;
if (
this.initialized &&
this.famId === opts.famId &&
this.actorId === opts.actorId &&
this.actorType === opts.actorType
)
return;
}
this.cleanup();
this.destroyed = false;
this.famId = opts.famId; this.famId = opts.famId;
this.actorId = opts.actorId; this.actorId = opts.actorId;
this.actorType = opts.actorType; this.actorType = opts.actorType;
this.actorName = opts.actorName; this.actorName = opts.actorName;
this.actorColor = opts.actorColor; this.actorColor = opts.actorColor;
this.pbToken = opts.pbToken || ''; this.pbToken = opts.pbToken || '';
this.messages = [];
if (this.initialized && this.famId === opts.famId) return; this.typing = {};
if (this.initPromise) { this.unread = 0;
await this.initPromise;
if (this.initialized && this.famId === opts.famId) return;
}
this.cleanup();
this.destroyed = false;
this.lastSeenAt = Date.now(); this.lastSeenAt = Date.now();
this.initPromise = (async () => { this.initPromise = (async () => {
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms'; import { enhance, applyAction } from '$app/forms';
import { ViewHeader, CardGrid, Card, Button } from '$lib/components'; import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
let { data, form } = $props(); let { data, form } = $props();
@@ -24,7 +24,20 @@
{:else} {:else}
<CardGrid> <CardGrid>
<Card cols={2} title="Appearance"> <Card cols={2} title="Appearance">
<form method="POST" action="?/update" use:enhance> <form
method="POST"
action="?/update"
use:enhance={() => {
return async ({ result }) => {
// Apply the action result (so the success/error message
// shows) WITHOUT resetting the form — the default enhance
// clears name/email inputs on a successful submit.
if (result.type === 'success' || result.type === 'failure') {
await applyAction(result);
}
};
}}
>
<div style="margin-bottom:1rem"> <div style="margin-bottom:1rem">
<label style="display:block;font-size:0.85rem;color:#374151;margin-bottom:0.25rem"> <label style="display:block;font-size:0.85rem;color:#374151;margin-bottom:0.25rem">
Display Name Display Name
@@ -83,7 +96,9 @@
<p style="color:#059669;font-size:0.85rem;margin-bottom:0.5rem">Saved!</p> <p style="color:#059669;font-size:0.85rem;margin-bottom:0.5rem">Saved!</p>
{/if} {/if}
<Button type="submit" variant="primary">Save</Button> <div class="form-actions">
<Button type="submit" variant="primary">Update preferences</Button>
</div>
</form> </form>
</Card> </Card>
@@ -97,3 +112,11 @@
</Card> </Card>
</CardGrid> </CardGrid>
{/if} {/if}
<style>
.form-actions {
margin-top: 1.25rem;
padding-top: 1.25rem;
border-top: 1px solid #e5e7eb;
}
</style>
+6
View File
@@ -90,6 +90,8 @@ export const RULE_PARENT_SCOPED =
"famId = @request.auth.famId && @request.auth.role = 'parent'"; "famId = @request.auth.famId && @request.auth.role = 'parent'";
export const RULE_FAM_WRITE = "@request.body.famId = @request.auth.famId"; export const RULE_FAM_WRITE = "@request.body.famId = @request.auth.famId";
export const RULE_FAM_SCOPED = "famId = @request.auth.famId"; export const RULE_FAM_SCOPED = "famId = @request.auth.famId";
// Anyone in the family can read (list/view/subscribe) — members + parents.
export const RULE_FAM_READ = "famId = @request.auth.famId";
// fams has no self-referencing famId field; its record id IS the famId. // fams has no self-referencing famId field; its record id IS the famId.
export const RULE_OWN_FAM = "id = @request.auth.famId"; export const RULE_OWN_FAM = "id = @request.auth.famId";
@@ -270,6 +272,8 @@ export const SCHEMA_PLAN: CollectionPlanEntry[] = [
createRule: RULE_FAM_WRITE, createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED, updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED, deleteRule: RULE_FAM_SCOPED,
listRule: RULE_FAM_READ,
viewRule: RULE_FAM_READ,
})(ids), })(ids),
}, },
{ {
@@ -286,6 +290,8 @@ export const SCHEMA_PLAN: CollectionPlanEntry[] = [
createRule: RULE_FAM_WRITE, createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED, updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED, deleteRule: RULE_FAM_SCOPED,
listRule: RULE_FAM_READ,
viewRule: RULE_FAM_READ,
})(ids), })(ids),
}, },
{ {