Fix issues from QA review: invited editor payload, auto-resize perf, resize-y conflict
C-01: Strip is_starred/recurrence_rule from payload for invited editors
(not in backend allowlist → would 403). Hide Star checkbox from
invited editor edit mode entirely.
W-01: Wrap auto-resize in requestAnimationFrame to batch with paint
cycle and avoid forced reflow on every keystroke.
S-01: Add comment documenting belt-and-suspenders scroll prevention.
S-02: Remove resize-y from textarea (conflicts with auto-grow which
resets height on keystroke, overriding manual resize).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43322db5ff
commit
bb39888d2e
@ -290,8 +290,10 @@ export default function EventDetailPanel({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = descRef.current;
|
const el = descRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
el.style.height = 'auto';
|
el.style.height = 'auto';
|
||||||
el.style.height = `${el.scrollHeight}px`;
|
el.style.height = `${el.scrollHeight}px`;
|
||||||
|
});
|
||||||
}, [editState.description, isEditing]);
|
}, [editState.description, isEditing]);
|
||||||
|
|
||||||
// Poll lock status in view mode for shared events (Stream A: real-time lock awareness)
|
// Poll lock status in view mode for shared events (Stream A: real-time lock awareness)
|
||||||
@ -376,11 +378,11 @@ export default function EventDetailPanel({
|
|||||||
end_datetime: endDt,
|
end_datetime: endDt,
|
||||||
all_day: data.all_day,
|
all_day: data.all_day,
|
||||||
location_id: data.location_id ? parseInt(data.location_id) : null,
|
location_id: data.location_id ? parseInt(data.location_id) : null,
|
||||||
is_starred: data.is_starred,
|
|
||||||
recurrence_rule: rule,
|
|
||||||
};
|
};
|
||||||
// Invited editors cannot change calendars — omit calendar_id from payload
|
// Invited editors are restricted to the backend allowlist — omit fields they cannot modify
|
||||||
if (!canModifyAsInvitee) {
|
if (!canModifyAsInvitee) {
|
||||||
|
payload.is_starred = data.is_starred;
|
||||||
|
payload.recurrence_rule = rule;
|
||||||
payload.calendar_id = data.calendar_id ? parseInt(data.calendar_id) : null;
|
payload.calendar_id = data.calendar_id ? parseInt(data.calendar_id) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,6 +550,7 @@ export default function EventDetailPanel({
|
|||||||
: event?.title || '';
|
: event?.title || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// onWheel stopPropagation: defence-in-depth with CalendarPage's panelOpen guard to prevent month-scroll bleed
|
||||||
<div className="flex flex-col h-full bg-card border-l border-border overflow-hidden" onWheel={(e) => e.stopPropagation()}>
|
<div className="flex flex-col h-full bg-card border-l border-border overflow-hidden" onWheel={(e) => e.stopPropagation()}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-5 py-4 border-b border-border shrink-0">
|
<div className="px-5 py-4 border-b border-border shrink-0">
|
||||||
@ -866,18 +869,6 @@ export default function EventDetailPanel({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Star for invited editors (no recurrence row shown) */}
|
|
||||||
{canModifyAsInvitee && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
id="panel-starred"
|
|
||||||
checked={editState.is_starred}
|
|
||||||
onChange={(e) => updateField('is_starred', (e.target as HTMLInputElement).checked)}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="panel-starred" className="text-xs">Starred</Label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{editState.recurrence_type === 'every_n_days' && (
|
{editState.recurrence_type === 'every_n_days' && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="panel-interval">Every how many days?</Label>
|
<Label htmlFor="panel-interval">Every how many days?</Label>
|
||||||
@ -955,7 +946,7 @@ export default function EventDetailPanel({
|
|||||||
value={editState.description}
|
value={editState.description}
|
||||||
onChange={(e) => updateField('description', e.target.value)}
|
onChange={(e) => updateField('description', e.target.value)}
|
||||||
placeholder="Add a description..."
|
placeholder="Add a description..."
|
||||||
className="text-sm resize-y flex-1 min-h-[80px]"
|
className="text-sm flex-1 min-h-[80px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -146,8 +146,10 @@ export default function EventForm({ event, templateData, templateName, initialSt
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = descRef.current;
|
const el = descRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
el.style.height = 'auto';
|
el.style.height = 'auto';
|
||||||
el.style.height = `${el.scrollHeight}px`;
|
el.style.height = `${el.scrollHeight}px`;
|
||||||
|
});
|
||||||
}, [formData.description]);
|
}, [formData.description]);
|
||||||
|
|
||||||
const selectableCalendars = calendars.filter((c) => !c.is_system);
|
const selectableCalendars = calendars.filter((c) => !c.is_system);
|
||||||
@ -269,7 +271,7 @@ export default function EventForm({ event, templateData, templateName, initialSt
|
|||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
placeholder="Add a description..."
|
placeholder="Add a description..."
|
||||||
className="min-h-[80px] resize-y text-sm"
|
className="min-h-[80px] text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user