Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
44b5219
fix(backstage): key the cargo lock on the Admin role, not on the wide…
arkgast Aug 28, 2026
4758146
fix(backstage): mirror every provisioning refusal, and say which one …
arkgast Aug 28, 2026
f857ba4
fix(beacon,ui): screen assignedBy before Auth; associate the cargo notes
arkgast Aug 28, 2026
367b76a
docs(rules,specs): pin the unconstrained email, correct the supersede…
arkgast Aug 28, 2026
6e47517
fix(types,beacon): make the provisioning refusal reasons one typed co…
arkgast Aug 28, 2026
85a1a5d
test(rules): assert the client cargo mirror against the real rules en…
arkgast Aug 28, 2026
c354f89
fix(backstage): mirror both mint refusals; close the invite state races
arkgast Aug 28, 2026
10031c4
fix(backstage): answer both conjuncts before promising the row action
arkgast Aug 28, 2026
7f10a35
refactor: state each rationale once, not at both sites
arkgast Aug 28, 2026
3c1df34
test,docs: name the two gaps the parity test cannot cover
arkgast Aug 28, 2026
7011747
chore: reviews
arkgast Aug 28, 2026
7ca1abb
fix(beacon): make every fail-closed claims-sync path say so
arkgast Aug 28, 2026
7e1b8f0
fix(backstage): map the drawer's refusals; stop the invite from erasi…
arkgast Aug 28, 2026
c064ed0
fix(beacon): log the drop that erases the evidence; split anomaly fro…
arkgast Aug 28, 2026
cadf1aa
fix(backstage): reset the invite panel per member; make the refusal t…
arkgast Aug 28, 2026
78e2d7e
chore: reviews
arkgast Aug 28, 2026
1787ab1
fix(backstage): the invite mail cannot be dropped by a component unmo…
arkgast Aug 28, 2026
c00fefe
fix(backstage,ui): fail closed on a cargo the editor cannot resolve
arkgast Aug 28, 2026
499253f
test(rules): make the parity test catch a rules LOOSENING, and stop m…
arkgast Aug 28, 2026
fec9b56
fix(beacon): tag the last untagged refusal; make two log guards falsi…
arkgast Aug 28, 2026
0ca459a
docs(plans): cite the shipped guards by symbol, not by line number
arkgast Aug 28, 2026
3d933fe
fix(beacon): tag Identity Toolkit's own invalid-email, not just the s…
arkgast Aug 28, 2026
8c4cf1c
test(rules): make each converse row prove its own lane reachability
arkgast Aug 28, 2026
0239ddf
fix(beacon,rules): pin the null-vs-throw contract; dissolve the impor…
arkgast Aug 28, 2026
1f20cde
chore: reviews
arkgast Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/backstage/src/components/member-home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useActivitiesByTerm } from "../features/activities/hooks/use-activities
import { useInitiativesByTerm } from "../features/initiatives/hooks/use-initiatives-by-term";
import { usePositions } from "../features/positions/hooks/use-positions";
import { joinYear } from "../features/members/lib/member-display";
import { isSelfMember } from "../features/members/lib/member-permissions";
import { summarizeParticipations } from "../features/members/lib/participation-summary";
import { MemberPointsSummary } from "../features/members/components/member-points-summary";
import { MemberCredentialCard } from "../features/members/components/member-credential-card";
Expand Down Expand Up @@ -92,7 +93,7 @@ export function MemberHome() {
// at a role, so the honest mirror is doc ownership — NOT the CASL own-doc grant, which
// only members carrying the built-in Member role hold (a roles:["Treasury"] principal
// would lose a self-edit the rules would have accepted).
const canEditSelf = member.uid !== undefined && member.uid === uid;
const canEditSelf = isSelfMember(member, uid);

const cargoId = member.positions?.[termId]?.cargoId ?? null;
const cargo = cargoId ? positionsById.get(cargoId) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ vi.mock("@tanstack/react-router", () => ({
Link: ({ children }: { children: React.ReactNode }) => <a href="#">{children}</a>,
}));

// The drawer now reads the caller's uid to decide whether the row it opened is the caller's
// OWN (the members table lists it too), which makes the edit a SELF-assignment. Mocked as a
// factory with no `importOriginal`: lib/auth/auth builds its store from getFirebase().auth at
// module scope, so merely evaluating the real module initializes Firebase and the whole file
// fails to collect. uid "someone-else" keeps every case below a non-self edit.
vi.mock("../../../lib/auth/auth", () => ({
useAuth: () => ({ user: { uid: "someone-else" }, claims: { roles: ["Admin"] } }),
}));

const m: Member = {
id: "1",
name: "Ana Gómez",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { MemberForm } from "./member-form";
import { joinYear, memberPositionLabel } from "../lib/member-display";
import { memberFormDefaults } from "../lib/member-form-defaults";
import { useMemberPhoto } from "../hooks/use-member-photo";
import { isSelfMember } from "../lib/member-permissions";
import { Can } from "../../../lib/authz/ability-context";
import { useAuth } from "../../../lib/auth/auth";
import { useCan } from "../../../lib/authz/use-can";

interface MemberDrawerProps {
Expand Down Expand Up @@ -136,7 +138,9 @@ function EditBody({
onSubmit: (data: MemberInput) => Promise<void>;
}) {
const { onUpload, onRemove } = useMemberPhoto(member.id);
const { canAssignBoardSeat } = useCan();
const { canAssignBoardSeat, isAdmin } = useCan();
// The table lists the caller's own row too, so this drawer can be a self-assignment.
const uid = useAuth().user?.uid;
return (
<div className="flex flex-col gap-6">
<ImageUploader
Expand All @@ -151,6 +155,9 @@ function EditBody({
defaultValues={memberFormDefaults(member)}
submitLabel="Guardar"
allowPowerGrants={canAssignBoardSeat}
allowReplacePowerCargo={isAdmin}
assignerIsAdmin={isAdmin}
isSelfAssignment={isSelfMember(member, uid)}
onSubmit={onSubmit}
/>
</div>
Expand Down
Loading
Loading