Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
We'll document all notable changes in this file, including bug fixes, enhancements, and dependency updates.
Dates should be in`YYYY-MM-DD` format and versions are in [semantic versioning](http://semver.org/) format.

## v0.10.6 2026-11-07

### Fixed

- Process pages no longer show "Unknown role" to people who aren't logged in.

## v0.10.5 2026-11-07

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adminima",
"version": "0.10.5",
"version": "0.10.6",
"private": true,
"scripts": {
"dev": "vite dev",
Expand Down
293 changes: 151 additions & 142 deletions src/lib/ARCI.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { getDB } from '#routes/+layout.svelte';
import Options from './Options.svelte';
import { mutate } from '#routes/errors.svelte.ts';
import { getOrg } from '#routes/org/[orgid]/+layout.svelte';

interface Props {
how: HowRow;
Expand All @@ -17,6 +18,12 @@

let { roles, how, verbose, editable }: Props = $props();

const orgContext = getOrg();

// Mirrors the roles RLS policy: only members, or anyone if the org is public, can see its roles.
// Without them, every assignment would render as "Unknown role", so show nothing instead.
let canSeeRoles = $derived(orgContext().member || orgContext().org.visibility === 'public');

const dbContext = getDB();
const db = $derived(dbContext());

Expand Down Expand Up @@ -50,152 +57,154 @@
};
</script>

<div class="arci" class:verbose>
<div class="slot">
<div class="control">
{#if !verbose}
<Level level="responsible" />
{/if}
{#if editable}
<Options
id="responsible-chooser"
tip="Add a role to be responsible for completing this step."
{options}
view={{ snippet: RoleItem, data: roles }}
empty={false}
searchable={roleSearch}
active={options.length >= 1}
bind:selection={responsible}
change={async (value) => {
if (value) {
const { error } = await mutate(
db.addHowRCI(how, value, 'responsible'),
"Couldn't add role."
);
if (error) return false;
responsible = undefined;
}
return true;
}}
/>
{/if}
{#if canSeeRoles}
<div class="arci" class:verbose>
<div class="slot">
<div class="control">
{#if !verbose}
<Level level="responsible" />
{/if}
{#if editable}
<Options
id="responsible-chooser"
tip="Add a role to be responsible for completing this step."
{options}
view={{ snippet: RoleItem, data: roles }}
empty={false}
searchable={roleSearch}
active={options.length >= 1}
bind:selection={responsible}
change={async (value) => {
if (value) {
const { error } = await mutate(
db.addHowRCI(how, value, 'responsible'),
"Couldn't add role."
);
if (error) return false;
responsible = undefined;
}
return true;
}}
/>
{/if}
</div>
<div class="roles">
{#each how.responsible as responsible}
<RoleLink role={roles.find((r) => r.id === responsible)}
>{#if editable}
<Button
chromeless
tip="Remove this role from the responsible list."
action={() =>
mutate(db.removeHowRCI(how, responsible, 'responsible'), "Couldn't remove role.")}
>{Delete}</Button
>
{/if}</RoleLink
>
{/each}
{#if verbose}
is <Level level="responsible" verbose /> for completing this process.
{/if}
</div>
</div>
<div class="roles">
{#each how.responsible as responsible}
<RoleLink role={roles.find((r) => r.id === responsible)}
>{#if editable}
<Button
chromeless
tip="Remove this role from the responsible list."
action={() =>
mutate(db.removeHowRCI(how, responsible, 'responsible'), "Couldn't remove role.")}
>{Delete}</Button
>
{/if}</RoleLink
>
{/each}
{#if verbose}
is <Level level="responsible" verbose /> for completing this process.
{/if}
</div>
</div>
<div class="slot">
<div class="control">
{#if !verbose}
<Level level="consulted" />
{/if}
{#if editable}
<Options
id="consulted-chooser"
tip="Add a role to be consulted in this step."
{options}
view={{ snippet: RoleItem, data: roles }}
empty={false}
active={options.length >= 1}
bind:selection={consulted}
searchable={roleSearch}
change={async (value) => {
if (value) {
const { error } = await mutate(
db.addHowRCI(how, value, 'consulted'),
"Couldn't add role."
);
if (error) return false;
consulted = undefined;
}
return true;
}}
/>
{/if}
</div>
<div class="roles">
{#each how.consulted as consulted}
<RoleLink role={roles.find((r) => r.id === consulted)}
>{#if editable}
<Button
chromeless
tip="Remove this role from the consulted list."
action={() =>
mutate(db.removeHowRCI(how, consulted, 'consulted'), "Couldn't remove role.")}
>{Delete}</Button
>
{/if}</RoleLink
>
{/each}
{#if verbose}
is <Level level="consulted" verbose /> to complete this process.
{/if}
</div>
</div>
<div class="slot">
<div class="controls">
{#if !verbose}
<Level level="informed" />
{/if}
{#if editable}
<Options
id="informed-chooser"
tip="Add a role to be informed in this step."
{options}
view={{ snippet: RoleItem, data: roles }}
empty={false}
searchable={roleSearch}
bind:selection={informed}
active={options.length >= 1}
change={async (value) => {
if (value) {
const { error } = await mutate(
db.addHowRCI(how, value, 'informed'),
"Couldn't add role."
);
if (error) return false;
informed = undefined;
}
return true;
}}
/>
{/if}
<div class="slot">
<div class="control">
{#if !verbose}
<Level level="consulted" />
{/if}
{#if editable}
<Options
id="consulted-chooser"
tip="Add a role to be consulted in this step."
{options}
view={{ snippet: RoleItem, data: roles }}
empty={false}
active={options.length >= 1}
bind:selection={consulted}
searchable={roleSearch}
change={async (value) => {
if (value) {
const { error } = await mutate(
db.addHowRCI(how, value, 'consulted'),
"Couldn't add role."
);
if (error) return false;
consulted = undefined;
}
return true;
}}
/>
{/if}
</div>
<div class="roles">
{#each how.consulted as consulted}
<RoleLink role={roles.find((r) => r.id === consulted)}
>{#if editable}
<Button
chromeless
tip="Remove this role from the consulted list."
action={() =>
mutate(db.removeHowRCI(how, consulted, 'consulted'), "Couldn't remove role.")}
>{Delete}</Button
>
{/if}</RoleLink
>
{/each}
{#if verbose}
is <Level level="consulted" verbose /> to complete this process.
{/if}
</div>
</div>
<div class="roles">
{#each how.informed as informed}
<RoleLink role={roles.find((r) => r.id === informed)}
>{#if editable}
<Button
chromeless
tip="Remove this role from the informed list"
action={() =>
mutate(db.removeHowRCI(how, informed, 'informed'), "Couldn't remove role.")}
>{Delete}</Button
>
{/if}</RoleLink
>
{/each}
{#if verbose}
is <Level level="informed" verbose /> about this process and its outcomes.
{/if}
<div class="slot">
<div class="controls">
{#if !verbose}
<Level level="informed" />
{/if}
{#if editable}
<Options
id="informed-chooser"
tip="Add a role to be informed in this step."
{options}
view={{ snippet: RoleItem, data: roles }}
empty={false}
searchable={roleSearch}
bind:selection={informed}
active={options.length >= 1}
change={async (value) => {
if (value) {
const { error } = await mutate(
db.addHowRCI(how, value, 'informed'),
"Couldn't add role."
);
if (error) return false;
informed = undefined;
}
return true;
}}
/>
{/if}
</div>
<div class="roles">
{#each how.informed as informed}
<RoleLink role={roles.find((r) => r.id === informed)}
>{#if editable}
<Button
chromeless
tip="Remove this role from the informed list"
action={() =>
mutate(db.removeHowRCI(how, informed, 'informed'), "Couldn't remove role.")}
>{Delete}</Button
>
{/if}</RoleLink
>
{/each}
{#if verbose}
is <Level level="informed" verbose /> about this process and its outcomes.
{/if}
</div>
</div>
</div>
</div>
{/if}

<style>
.arci {
Expand Down
Loading
Loading