From 702061f9032e104b0c3514e1b46e50833aa6392b Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 9 Oct 2026 13:22:15 +1100 Subject: [PATCH 1/7] code cleanup --- .../AccessControlModal.styles.css | 7 + .../AccessControlModal.ts | 207 ++++++++++-------- .../access-control-modal/helpers.ts | 24 ++ 3 files changed, 146 insertions(+), 92 deletions(-) create mode 100644 src/components/access-control-modal/helpers.ts diff --git a/src/components/access-control-modal/AccessControlModal.styles.css b/src/components/access-control-modal/AccessControlModal.styles.css index b76dd5f47..d096e5b5c 100644 --- a/src/components/access-control-modal/AccessControlModal.styles.css +++ b/src/components/access-control-modal/AccessControlModal.styles.css @@ -104,6 +104,13 @@ align-self: stretch; } + .access-grants-directory-entry { + display: inline-flex; + align-items: center; + gap: 8px; + line-height: 1; + } + .access-grants-header { display: flex; align-items: center; diff --git a/src/components/access-control-modal/AccessControlModal.ts b/src/components/access-control-modal/AccessControlModal.ts index 423f25cbf..700d2a925 100644 --- a/src/components/access-control-modal/AccessControlModal.ts +++ b/src/components/access-control-modal/AccessControlModal.ts @@ -26,11 +26,16 @@ import '@/components/combobox-option' import styles from './AccessControlModal.styles.css' import type { ComboboxChangeEvent } from '@/components/combobox' import type { AccessChange, AccessControlBadge, AccessGrantEntry, AuthorizationSubjectSet, ChangedAccessGrant, PendingAccessGrant } from './types' +import { dedupeByKey, dedupeComboboxOptions, isHttpUri, type StringComboboxOptionData } from './helpers' function isDefined (value: T | undefined | null): value is T { return value !== undefined && value !== null } +type PendingAccessGrantCreationResult = + | { grant: PendingAccessGrant; principle: string } + | { error: string; principle: string } + @customElement('solid-ui-access-control-modal') export default class AccessControlModal extends WebComponent { static styles = styles @@ -324,7 +329,8 @@ export default class AccessControlModal extends WebComponent { type="button" variant="ghost" class="access-grants-pending-item-remove" - @click=${() => this.removePendingAccessGrant(index)} + data-pending-grant-index=${index} + @click=${this.onRemovePendingAccessGrantClick} > Remove ${grant.label} @@ -336,8 +342,6 @@ export default class AccessControlModal extends WebComponent { } private renderAccessGrantsSection () { - const accessGrantOptions = this.getAccessGrantSearchOptions() - return html`

Share with

@@ -350,10 +354,10 @@ export default class AccessControlModal extends WebComponent { @input=${this.onSearchInput} @change=${this.onSearchSelect} > - ${accessGrantOptions.map(option => html` + ${this.getAccessGrantEntries().map(({ subjectLabel }) => html` - ${option.label} + .value=${subjectLabel}> + ${subjectLabel} `)} @@ -459,7 +463,12 @@ export default class AccessControlModal extends WebComponent { return } - await this.commitPrinciplesFromInput() + try { + await this.commitPrinciplesFromInput() + } catch (error) { + this.failed = true + console.error('Failed to commit pending access grants', error) + } } private async onSaveClick () { @@ -468,7 +477,12 @@ export default class AccessControlModal extends WebComponent { } if (this.principalInputValue.trim()) { - await this.commitPrinciplesFromInput() + try { + await this.commitPrinciplesFromInput() + } catch (error) { + this.failed = true + console.error('Failed to commit pending access grants', error) + } } await this.saveAccessChanges() @@ -584,6 +598,17 @@ export default class AccessControlModal extends WebComponent { this.pendingAccessGrants = this.pendingAccessGrants.filter((_, pendingIndex) => pendingIndex !== index) } + private onRemovePendingAccessGrantClick (event: Event) { + const target = event.currentTarget as HTMLElement | null + const index = target?.dataset.pendingGrantIndex + + if (index === undefined) { + return + } + + this.removePendingAccessGrant(Number.parseInt(index, 10)) + } + private hasUnsavedChanges (): boolean { return Boolean( this.pendingAccessGrants.length || @@ -666,15 +691,20 @@ export default class AccessControlModal extends WebComponent { inputSnapshot?: string ) { const snapshot = inputSnapshot ?? this.principalInputValue.trim() - const pendingGrants = await Promise.all(principles.map(async principle => this.createPendingAccessGrant(principle, role, preferredLabel))) - const newGrants = pendingGrants.filter(isDefined) + const pendingGrantResults = await Promise.all(principles.map(async principle => this.createPendingAccessGrant(principle, role, preferredLabel))) + const newGrants = pendingGrantResults.flatMap(result => ('grant' in result ? [result.grant] : [])) + const failedGrants = pendingGrantResults + .filter((result): result is { error: string; principle: string } => 'error' in result) + .map(result => `${result.principle} (${result.error})`) if (!newGrants.length) { + this.logPendingGrantFailures(failedGrants, false) return } const merged = [...this.pendingAccessGrants, ...newGrants] this.pendingAccessGrants = this.dedupePendingAccessGrants(merged) + this.logPendingGrantFailures(failedGrants, true) if (this.principalInputValue.trim() === snapshot) { this.principalInputValue = '' @@ -685,34 +715,56 @@ export default class AccessControlModal extends WebComponent { principle: string, role: AccessRole = this.addAccessRoleValue, preferredLabel?: string - ): Promise { - const normalizedPrinciple = this.normalizeAccessPrincipleInput(principle) - const resolvedPrinciple = await solidLogicSingleton.acl.classifyAccessControlSubject(normalizedPrinciple) - const fallbackKind = this.isHttpUri(principle) ? 'agent' : undefined - const subjectType = resolvedPrinciple?.kind ?? fallbackKind - const subjectValue = resolvedPrinciple?.subjectValue ?? normalizedPrinciple - - if (!subjectType) { - console.error(`Could not classify access target: ${principle}`) - return undefined - } + ): Promise { + try { + const normalizedPrinciple = principle.trim() + const resolvedPrinciple = await solidLogicSingleton.acl.classifyAccessControlSubject(normalizedPrinciple) + const fallbackKind = this.isHttpUri(principle) ? 'agent' : undefined + const subjectType = resolvedPrinciple?.kind ?? fallbackKind + const subjectValue = resolvedPrinciple?.subjectValue ?? normalizedPrinciple - if (subjectType === 'origin') { - console.error(`Origin access grants are not supported yet: ${principle}`) - return undefined - } + if (!subjectType) { + return { + principle, + error: 'Could not classify access target' + } + } - const label = preferredLabel ?? await this.resolvePendingAccessGrantLabel(subjectValue, principle) + if (subjectType === 'origin') { + return { + principle, + error: 'Origin access grants are not supported yet' + } + } - return { - subjectType, - subjectValue, - role, - label + const label = preferredLabel ?? await this.resolvePendingAccessGrantLabel(subjectValue, principle) + + return { + principle, + grant: { + subjectType, + subjectValue, + role, + label + } + } + } catch (error) { + return { + principle, + error: String(error) + } } } - private async resolvePendingAccessGrantLabel (subjectValue: string, fallbackLabel: string): Promise { + private logPendingGrantFailures (failedGrants: string[], partial: boolean) { + if (!failedGrants.length) { + return + } + + console.error(partial ? 'Failed to add some access grants:' : 'Failed to add access grants:', failedGrants) + } + + private async resolvePendingAccessGrantLabel (subjectValue: string, fallbackLabel: string = subjectValue): Promise { try { const subject = sym(subjectValue) await solidLogicSingleton.store.fetcher.load(subject.doc()) @@ -724,30 +776,15 @@ export default class AccessControlModal extends WebComponent { } private dedupePendingAccessGrants (draftGrants: PendingAccessGrant[]): PendingAccessGrant[] { - const seen = new Set() - - return draftGrants.filter(draftGrant => { - const key = `${draftGrant.subjectType}:${draftGrant.subjectValue}` - if (seen.has(key)) { - return false - } - - seen.add(key) - return true - }) + return dedupeByKey(draftGrants, draftGrant => `${draftGrant.subjectType}:${draftGrant.subjectValue}`) } private readonly accessPrincipleOptionsProvider = defineAsyncComboboxOptionsProvider(async (filter: string) => { - const query = this.getPrincipleSearchTerm(filter) - const urlOption = this.isHttpUri(query) ? await this.createUrlOption(query) : undefined - const preferredOptions = this.dedupeComboboxOptions([urlOption].filter((option): option is ComboboxOptionData => Boolean(option))) + const query = filter.trim() + const preferredOptions = await this.getPreferredAccessPrincipleOptions(query) if (query.length < 2) { - return preferredOptions.length ? preferredOptions : [{ - label: 'Type at least 2 characters to search', - value: '', - selectable: false - }] + return this.getShortQueryOptions(preferredOptions) } let entries: DirectoryEntry[] = [] @@ -758,7 +795,7 @@ export default class AccessControlModal extends WebComponent { sources: DEFAULT_DIRECTORY_SOURCES }) } catch (error) { - if (!urlOption) { + if (!preferredOptions.length) { throw error } } @@ -767,14 +804,35 @@ export default class AccessControlModal extends WebComponent { return [...preferredOptions, ...options] }) - private async createUrlOption (uri: string): Promise { + private getShortQueryOptions (preferredOptions: ComboboxOptionData[]): ComboboxOptionData[] { + if (preferredOptions.length) { + return preferredOptions + } + + return [{ + label: 'Type at least 2 characters to search', + value: '', + selectable: false + }] + } + + private async getPreferredAccessPrincipleOptions (query: string): Promise { + if (!isHttpUri(query)) { + return [] + } + + const urlOption = await this.createUrlOption(query) + return dedupeComboboxOptions(urlOption ? [urlOption] : []) + } + + private async createUrlOption (uri: string): Promise { try { await solidLogicSingleton.store.fetcher.load(sym(uri).doc()) } catch { return undefined } - const labelText = await this.resolvePendingAccessGrantLabel(uri, uri) + const labelText = await this.resolvePendingAccessGrantLabel(uri) return { label: labelText === uri ? `Use ${uri}` : labelText, @@ -782,23 +840,6 @@ export default class AccessControlModal extends WebComponent { } } - private dedupeComboboxOptions (options: ComboboxOptionData[]): ComboboxOptionData[] { - const seen = new Set() - - return options.filter(option => { - if (typeof option.value !== 'string' || !option.value) { - return false - } - - if (seen.has(option.value)) { - return false - } - - seen.add(option.value) - return true - }) - } - private directoryEntryToOption (entry: DirectoryEntry): ComboboxOptionData { return { label: entry.label, @@ -809,7 +850,9 @@ export default class AccessControlModal extends WebComponent { private directoryEntryToOptionTemplate (entry: DirectoryEntry) { return html` - + + ${this.renderDirectoryEntryIcon(entry)} ${entry.label} @@ -832,30 +875,10 @@ export default class AccessControlModal extends WebComponent { return nothing } - private getAccessGrantSearchOptions (): ComboboxOptionData[] { - return this.getAccessGrantEntries().map(({ subjectLabel }) => ({ - label: subjectLabel, - value: subjectLabel - })) - } - - private getPrincipleSearchTerm (value: string): string { - const lastCommaIndex = value.lastIndexOf(',') - if (lastCommaIndex < 0) { - return value.trim() - } - - return value.slice(lastCommaIndex + 1).trim() - } - private isHttpUri (value: string): boolean { return value.startsWith('http://') || value.startsWith('https://') } - private normalizeAccessPrincipleInput (value: string): string { - return value.trim() - } - private async onCopyLinkClick (event: Event) { event.preventDefault() diff --git a/src/components/access-control-modal/helpers.ts b/src/components/access-control-modal/helpers.ts new file mode 100644 index 000000000..6a6b77f91 --- /dev/null +++ b/src/components/access-control-modal/helpers.ts @@ -0,0 +1,24 @@ +import type { ComboboxOptionData } from '@/components/combobox' + +export type StringComboboxOptionData = ComboboxOptionData & { value: string } + +export function isHttpUri (possibleUri: string): boolean { + return possibleUri.startsWith('http://') || possibleUri.startsWith('https://') +} + +export function dedupeByKey (items: T[], getKey: (item: T) => string | undefined): T[] { + const uniqueItems = new Map() + + for (const item of items) { + const key = getKey(item) + if (key && !uniqueItems.has(key)) { + uniqueItems.set(key, item) + } + } + + return [...uniqueItems.values()] +} + +export function dedupeComboboxOptions (options: StringComboboxOptionData[]): StringComboboxOptionData[] { + return dedupeByKey(options, option => option.value) +} From c27dfd4a76150f93d1e92da1a3e9a32dc65e20d4 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 9 Oct 2026 17:16:09 +1100 Subject: [PATCH 2/7] implement general access dialog section Prompt: tests were created by ai Co-authored-by: GPT-5.4 Mini --- .../AccessControlModal.styles.css | 18 +- .../AccessControlModal.ts | 197 ++++++++++++++---- src/components/combobox/Combobox.ts | 10 +- test/components/access-control-modal.test.ts | 87 +++++++- 4 files changed, 260 insertions(+), 52 deletions(-) diff --git a/src/components/access-control-modal/AccessControlModal.styles.css b/src/components/access-control-modal/AccessControlModal.styles.css index d096e5b5c..48f6d9355 100644 --- a/src/components/access-control-modal/AccessControlModal.styles.css +++ b/src/components/access-control-modal/AccessControlModal.styles.css @@ -238,7 +238,7 @@ align-items: flex-start; gap: 10px; - --access-role-select-width: 110px; + --access-role-select-width: 120px; solid-ui-combobox, solid-ui-input { @@ -252,6 +252,17 @@ align-items: center; gap: 15px; align-self: stretch; + + .access-grants-copy-link-button-label { + font-size: var(--solid-ui-font-size-xs, .75rem); + font-weight: 500; + } + + .access-grants-copy-link-button icon-lucide-link { + width: 12px; + height: 12px; + aspect-ratio: 1/1; + } } .access-grants-general-share { @@ -338,6 +349,11 @@ margin-top: 0; } + .access-role-select--general { + font-size: var(--solid-ui-font-size-xs, .75rem); + font-weight: 500; + } + .access-grants-list li .access-role-select--compact { width: 110px; min-width: 110px; diff --git a/src/components/access-control-modal/AccessControlModal.ts b/src/components/access-control-modal/AccessControlModal.ts index 700d2a925..6ce1b5bd7 100644 --- a/src/components/access-control-modal/AccessControlModal.ts +++ b/src/components/access-control-modal/AccessControlModal.ts @@ -5,7 +5,7 @@ import { property, query, state } from 'lit/decorators.js' import { label } from '@/utils/label' import { findImage } from '@/widgets' import type Dialog from '@/components/dialog' -import { ACCESS_ROLES, DEFAULT_DIRECTORY_SOURCES, solidLogicSingleton, type AccessMode, type AccessRole, type AccessSubject, type Authorization, type DirectoryEntry } from 'solid-logic' +import { ACCESS_ROLES, Authenticated, DEFAULT_DIRECTORY_SOURCES, PUBLIC_ACCESS_ROLES, Public, solidLogicSingleton, type AccessMode, type AccessRole, type AccessSubject, type Authorization, type DirectoryEntry } from 'solid-logic' import { defineAsyncComboboxOptionsProvider, type ComboboxOptionData } from '@/components/combobox' import { sym } from 'rdflib' @@ -53,7 +53,10 @@ export default class AccessControlModal extends WebComponent { private accessor addAccessRoleValue: AccessRole = 'Viewer' @state() - private accessor sharedAccessRoleValue: AccessRole = 'No Access' + private accessor authenticatedAccessRoleValue: AccessRole = 'No Access' + + @state() + private accessor publicAccessRoleValue: AccessRole = 'No Access' @state() private accessor searchValue: string = '' @@ -81,6 +84,8 @@ export default class AccessControlModal extends WebComponent { if (changedProperties.has('accessGrants')) { this.accessGrantRoles = this.accessGrants?.map(item => this.getAuthorizationRole(item)) ?? [] + this.authenticatedAccessRoleValue = this.getAuthenticatedAccessRole() + this.publicAccessRoleValue = this.getPublicAccessRole() void this.refreshAccessGrantLabels() } } @@ -133,9 +138,13 @@ export default class AccessControlModal extends WebComponent { }) } + private getSharedAccessGrantEntries () { + return this.getAccessGrantEntries().filter(({ authorization }) => !this.isGeneralAccessAuthorization(authorization)) + } + private renderAccessGrants() { const query = this.searchValue.trim().toLowerCase() - const accessGrants = this.getAccessGrantEntries().filter(({ subjectLabel }) => { + const accessGrants = this.getSharedAccessGrantEntries().filter(({ subjectLabel }) => { if (!query) { return true } @@ -230,6 +239,30 @@ export default class AccessControlModal extends WebComponent { return solidLogicSingleton.acl.roleFromModes(authorization.mode) } + private isGeneralAccessAuthorization (authorization: Authorization): boolean { + return authorization.agentClass.includes(Authenticated.iri) || authorization.agentClass.includes(Public.iri) + } + + private getAuthenticatedAccessRole (): AccessRole { + const authorization = (this.accessGrants ?? []).find(item => + item.agentClass.includes(Authenticated.iri) + ) + + return authorization ? this.getAuthorizationRole(authorization) : 'No Access' + } + + private getPublicAccessRole (): AccessRole { + const authorization = (this.accessGrants ?? []).find(item => + item.agentClass.includes(Public.iri) + ) + + if (!authorization) { + return 'No Access' + } + + return solidLogicSingleton.acl.publicRoleFromModes(authorization.mode) + } + private getRoleValueFromEvent (event: Event, fallback: AccessRole = 'Viewer'): AccessRole { const selectedValue = this.getSelectedComboboxOptionValue(event) @@ -254,19 +287,34 @@ export default class AccessControlModal extends WebComponent { ` } - private renderModeSelector (variant: 'add' | 'general' = 'add') { + private renderModeSelector (variant: 'add' | 'authenticated' | 'public' = 'add') { const className = variant === 'add' ? 'access-role-select access-role-select--top' - : 'access-role-select access-role-select--compact' - const value = variant === 'add' ? this.addAccessRoleValue : this.sharedAccessRoleValue + : variant === 'authenticated' + ? 'access-role-select access-role-select--compact access-role-select--general access-grants-role access-grants-role--editable access-role-select--authenticated' + : 'access-role-select access-role-select--compact access-role-select--general access-grants-role access-grants-role--editable access-role-select--public' + const value = variant === 'add' + ? this.addAccessRoleValue + : variant === 'authenticated' + ? this.authenticatedAccessRoleValue + : this.publicAccessRoleValue + const onChange = variant === 'add' + ? this.onAddAccessRoleInput + : variant === 'authenticated' + ? this.onAuthenticatedAccessRoleInput + : this.onPublicAccessRoleInput return html` - ${variant === 'add' ? this.renderAddRoleOptions() : this.renderGeneralRoleOptions()} + ${variant === 'add' + ? this.renderAddRoleOptions() + : variant === 'authenticated' + ? this.renderAuthenticatedRoleOptions() + : this.renderPublicRoleOptions()} ` } @@ -279,12 +327,18 @@ export default class AccessControlModal extends WebComponent { `) } - private renderGeneralRoleOptions () { + private renderAuthenticatedRoleOptions () { return ACCESS_ROLES.map(role => html` ${role} `) } + private renderPublicRoleOptions () { + return PUBLIC_ACCESS_ROLES.map(role => html` + ${role} + `) + } + private renderGrantRoleOptions () { return ACCESS_ROLES.map(role => html` ${role === 'No Access' ? 'Remove' : role} @@ -354,7 +408,7 @@ export default class AccessControlModal extends WebComponent { @input=${this.onSearchInput} @change=${this.onSearchSelect} > - ${this.getAccessGrantEntries().map(({ subjectLabel }) => html` + ${this.getSharedAccessGrantEntries().map(({ subjectLabel }) => html` ${subjectLabel} @@ -379,19 +433,38 @@ export default class AccessControlModal extends WebComponent { @click=${this.onCopyLinkClick} > - Copy Link + Copy Link
- diff --git a/test/components/access-control-modal.test.ts b/test/components/access-control-modal.test.ts index 186552fc3..294bba59f 100644 --- a/test/components/access-control-modal.test.ts +++ b/test/components/access-control-modal.test.ts @@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const planGrant = vi.fn() const planRevoke = vi.fn() +const planPublicRead = vi.fn() const applyPlan = vi.fn() const classifyAccessControlSubject = vi.fn() @@ -24,6 +25,7 @@ vi.mock('solid-logic', async (importOriginal) => { ...actual.solidLogicSingleton.acl, classifyAccessControlSubject, planGrant, + planPublicRead, planRevoke, applyPlan }, @@ -46,6 +48,7 @@ describe('AccessControlModal submit', () => { beforeEach(async () => { planGrant.mockReset() planRevoke.mockReset() + planPublicRead.mockReset() applyPlan.mockReset() classifyAccessControlSubject.mockReset() classifyAccessControlSubject.mockImplementation(async (principle: string) => ({ @@ -53,6 +56,7 @@ describe('AccessControlModal submit', () => { subjectValue: principle })) planGrant.mockResolvedValue({ target: 'https://example.com/resource.ttl.acl', deletes: [], inserts: [] }) + planPublicRead.mockResolvedValue({ target: 'https://example.com/resource.ttl.acl', deletes: [], inserts: [] }) applyPlan.mockResolvedValue(new Response('ok', { status: 200 })) await import('../../src/components/access-control-modal/AccessControlModal') }) @@ -181,6 +185,30 @@ describe('AccessControlModal submit', () => { expect(element.pendingAccessGrants).toEqual([]) }) + it('persists authenticated and public access changes on save', async () => { + const element = document.createElement('solid-ui-access-control-modal') as any + element.subjectUri = 'https://example.com/resource.ttl' + element.accessGrants = [] + element.authenticatedAccessRoleValue = 'Editor' + element.publicAccessRoleValue = 'Viewer' + + await element.onSaveClick() + + expect(planGrant).toHaveBeenNthCalledWith( + 1, + 'https://example.com/resource.ttl', + { type: 'agentClass', iri: 'http://www.w3.org/ns/auth/acl#AuthenticatedAgent' }, + ['Read', 'Write'] + ) + expect(planGrant).toHaveBeenNthCalledWith( + 2, + 'https://example.com/resource.ttl', + { type: 'agentClass', iri: 'http://xmlns.com/foaf/0.1/Agent' }, + ['Read'] + ) + expect(applyPlan).toHaveBeenCalledTimes(2) + }) + it('reads the selected role from the rendered combobox change event', async () => { const element = document.createElement('solid-ui-access-control-modal') as any document.body.appendChild(element) @@ -199,7 +227,8 @@ describe('AccessControlModal submit', () => { })) expect(element.addAccessRoleValue).toBe('Editor') - expect(element.sharedAccessRoleValue).toBe('No Access') + expect(element.authenticatedAccessRoleValue).toBe('No Access') + expect(element.publicAccessRoleValue).toBe('No Access') document.body.removeChild(element) }) @@ -209,14 +238,15 @@ describe('AccessControlModal submit', () => { element.subjectUri = 'https://example.com/resource.ttl' element.principalInputValue = 'https://alice.example.com/profile/card.ttl#me' element.addAccessRoleValue = 'Viewer' - element.sharedAccessRoleValue = 'No Access' + element.authenticatedAccessRoleValue = 'No Access' + element.publicAccessRoleValue = 'No Access' await element.onSubmit(new Event('submit')) document.body.appendChild(element) await element.updateComplete - const sharedRoleSelect = element.shadowRoot.querySelector('solid-ui-combobox.access-role-select--compact') + const sharedRoleSelect = element.shadowRoot.querySelector('solid-ui-combobox.access-role-select--authenticated') sharedRoleSelect.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, @@ -229,7 +259,8 @@ describe('AccessControlModal submit', () => { })) expect(element.addAccessRoleValue).toBe('Viewer') - expect(element.sharedAccessRoleValue).toBe('Editor') + expect(element.authenticatedAccessRoleValue).toBe('Editor') + expect(element.publicAccessRoleValue).toBe('No Access') expect(element.pendingAccessGrants).toEqual([ expect.objectContaining({ role: 'Viewer' @@ -274,20 +305,24 @@ describe('AccessControlModal submit', () => { expect(element.getDialogTitle()).toBe('Share this resource') }) - it('hides no access in the add selector and keeps no access in the general selector', async () => { + it('hides no access in the add selector, keeps it in the authenticated selector, and exposes dokieli public roles', async () => { const element = document.createElement('solid-ui-access-control-modal') as any document.body.appendChild(element) await element.updateComplete const addRoleSelect = element.shadowRoot.querySelector('solid-ui-combobox.access-role-select--top') - const generalRoleSelect = element.shadowRoot.querySelector('solid-ui-combobox.access-role-select--compact') + const authenticatedRoleSelect = element.shadowRoot.querySelector('solid-ui-combobox.access-role-select--authenticated') + const publicRoleSelect = element.shadowRoot.querySelector('solid-ui-combobox.access-role-select--public') const addOptions = [...addRoleSelect.querySelectorAll('solid-ui-combobox-option')].map((option: Element) => option.textContent?.trim()) - const generalOptions = [...generalRoleSelect.querySelectorAll('solid-ui-combobox-option')].map((option: Element) => option.textContent?.trim()) + const authenticatedOptions = [...authenticatedRoleSelect.querySelectorAll('solid-ui-combobox-option')].map((option: Element) => option.textContent?.trim()) + const publicOptions = [...publicRoleSelect.querySelectorAll('solid-ui-combobox-option')].map((option: Element) => option.textContent?.trim()) expect(addOptions).not.toContain('No Access') expect(addOptions).toContain('Viewer') - expect(generalOptions).toContain('No Access') + expect(authenticatedOptions).toContain('No Access') + expect(authenticatedOptions).toContain('Editor') + expect(publicOptions).toEqual(['No Access', 'Viewer']) document.body.removeChild(element) }) @@ -313,6 +348,42 @@ describe('AccessControlModal submit', () => { document.body.removeChild(element) }) + it('keeps authenticated and public grants out of the shared-with list', async () => { + const element = document.createElement('solid-ui-access-control-modal') as any + element.accessGrants = [ + { + agent: ['https://alice.example.com/profile/card.ttl#me'], + agentGroup: [], + agentClass: [], + mode: ['Read'] + }, + { + agent: [], + agentGroup: [], + agentClass: ['http://www.w3.org/ns/auth/acl#AuthenticatedAgent'], + mode: ['Read', 'Write'] + }, + { + agent: [], + agentGroup: [], + agentClass: ['http://xmlns.com/foaf/0.1/Agent'], + mode: ['Read'] + } + ] + + document.body.appendChild(element) + await element.updateComplete + + const sharedWithLabels = [...element.shadowRoot.querySelectorAll('.access-grants-list h3')].map((label: Element) => label.textContent?.trim()) + expect(sharedWithLabels).toEqual(['card.ttl']) + + const searchOptions = [...element.shadowRoot.querySelectorAll('solid-ui-combobox.access-grants-search-input solid-ui-combobox-option')] + .map((option: Element) => option.textContent?.trim()) + expect(searchOptions).toEqual(['card.ttl']) + + document.body.removeChild(element) + }) + it('persists a changed shared access grant on save', async () => { const element = document.createElement('solid-ui-access-control-modal') as any element.subjectUri = 'https://example.com/resource.ttl' From a2546fc4c85674e6c3a08f1cfce8203214653e27 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 9 Oct 2026 20:32:49 +1100 Subject: [PATCH 3/7] fix combobox reclick issue --- src/components/combobox/Combobox.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/combobox/Combobox.ts b/src/components/combobox/Combobox.ts index bd65ee885..864e79468 100644 --- a/src/components/combobox/Combobox.ts +++ b/src/components/combobox/Combobox.ts @@ -158,15 +158,8 @@ export default class Combobox extends FormControlComponent { .value=${this.displayValue} @keydown=${this.onInputKeyDown} @focus=${this.onInputFocus} - @input=${() => { - if (this.selectOnly) { - this.updateDisplayValue(this.controlElement?.value ?? '') - } else { - this.controlTrait.onInput() - } - - this.show() - }} + @click=${this.onInputClick} + @input=${() => this.selectOnly ? this.updateDisplayValue(this.controlElement?.value ?? '') : this.controlTrait.onInput()} /> @@ -453,6 +446,10 @@ export default class Combobox extends FormControlComponent { this.show() } + private onInputClick () { + this.show() + } + private openPopupFromKey (focusLast: boolean) { if (!this.open) { this.show({ focusLast }) From 4ca3692b16aa33cb62799bbc3daa12f583ba610f Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 9 Oct 2026 20:52:09 +1100 Subject: [PATCH 4/7] minor style, fit no access wording --- .../access-control-modal/AccessControlModal.styles.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/access-control-modal/AccessControlModal.styles.css b/src/components/access-control-modal/AccessControlModal.styles.css index 48f6d9355..cb0d0c17b 100644 --- a/src/components/access-control-modal/AccessControlModal.styles.css +++ b/src/components/access-control-modal/AccessControlModal.styles.css @@ -238,8 +238,6 @@ align-items: flex-start; gap: 10px; - --access-role-select-width: 120px; - solid-ui-combobox, solid-ui-input { --solid-ui-input-border-color: var(--solid-ui-color-slate-200, #E2E8F0); @@ -268,7 +266,7 @@ .access-grants-general-share { display: flex; align-items: center; - gap: 15px; + gap: 10px; align-self: stretch; .access-grants-general-share-content { @@ -350,6 +348,9 @@ } .access-role-select--general { + width: 120px; + min-width: 120px; + flex: 0 0 120px; font-size: var(--solid-ui-font-size-xs, .75rem); font-weight: 500; } From 8d600f55906791d388ecb5e3e59f35a0ff993d57 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Fri, 9 Oct 2026 21:11:01 +1100 Subject: [PATCH 5/7] Address feedback from PR Prompt: Should I address the feedback about errors now, as I am going to implement error handling later. If so please add a temporary fix. Co-authored-by: GPT-5.4 Mini --- .../AccessControlModal.ts | 103 ++++++++++-------- src/components/access-control-modal/index.ts | 2 +- test/components/access-control-modal.test.ts | 78 +++++++++++++ 3 files changed, 138 insertions(+), 45 deletions(-) diff --git a/src/components/access-control-modal/AccessControlModal.ts b/src/components/access-control-modal/AccessControlModal.ts index 6ce1b5bd7..b6f30e48d 100644 --- a/src/components/access-control-modal/AccessControlModal.ts +++ b/src/components/access-control-modal/AccessControlModal.ts @@ -86,7 +86,7 @@ export default class AccessControlModal extends WebComponent { this.accessGrantRoles = this.accessGrants?.map(item => this.getAuthorizationRole(item)) ?? [] this.authenticatedAccessRoleValue = this.getAuthenticatedAccessRole() this.publicAccessRoleValue = this.getPublicAccessRole() - void this.refreshAccessGrantLabels() + this.refreshAccessGrantLabels() } } @@ -107,7 +107,7 @@ export default class AccessControlModal extends WebComponent { } } - return this.getAuthorizationSubjectLabel(authorization) + return this.getAuthorizationSubjectLabel(this.getSharedAuthorization(authorization)) })) if (this.accessGrants === grants) { @@ -118,6 +118,7 @@ export default class AccessControlModal extends WebComponent { private getAccessGrantEntries () { return (this.accessGrants ?? []) .map((authorization, index) => { + authorization = this.getSharedAuthorization(authorization) const subjectLabel = this.getAccessGrantSubjectLabel(authorization, index) return { @@ -139,10 +140,10 @@ export default class AccessControlModal extends WebComponent { } private getSharedAccessGrantEntries () { - return this.getAccessGrantEntries().filter(({ authorization }) => !this.isGeneralAccessAuthorization(authorization)) + return this.getAccessGrantEntries().filter(({ authorization }) => this.getAuthorizationSubjectIris(authorization).length > 0) } - private renderAccessGrants() { + private renderAccessGrants () { const query = this.searchValue.trim().toLowerCase() const accessGrants = this.getSharedAccessGrantEntries().filter(({ subjectLabel }) => { if (!query) { @@ -239,8 +240,11 @@ export default class AccessControlModal extends WebComponent { return solidLogicSingleton.acl.roleFromModes(authorization.mode) } - private isGeneralAccessAuthorization (authorization: Authorization): boolean { - return authorization.agentClass.includes(Authenticated.iri) || authorization.agentClass.includes(Public.iri) + private getSharedAuthorization (authorization: Authorization): Authorization { + return { + ...authorization, + agentClass: authorization.agentClass.filter(iri => iri !== Authenticated.iri && iri !== Public.iri) + } } private getAuthenticatedAccessRole (): AccessRole { @@ -493,39 +497,39 @@ export default class AccessControlModal extends WebComponent { return `Share "${subjectLabel}"` } - + protected render () { const dialogTitle = this.getDialogTitle() - + return html` - -
- - ${this.renderAddAccessForm()} - ${this.renderAccessGrantsSection()} - ${this.renderGeneralAccessSection()} - - - - - -
-
+ +
+ + ${this.renderAddAccessForm()} + ${this.renderAccessGrantsSection()} + ${this.renderGeneralAccessSection()} + + + + + +
+
` } @@ -545,7 +549,11 @@ export default class AccessControlModal extends WebComponent { } if (this.principalInputValue.trim()) { - await this.commitPrinciplesFromInputSafely() + const committed = await this.commitPrinciplesFromInputSafely() + + if (!committed) { + return + } } await this.saveAccessChanges() @@ -620,7 +628,7 @@ export default class AccessControlModal extends WebComponent { return } - void this.queuePendingPrinciples([option.value], this.addAccessRoleValue, option.label) + this.queuePendingPrinciples([option.value], this.addAccessRoleValue, option.label) } private onSearchInput (event: Event) { @@ -727,7 +735,7 @@ export default class AccessControlModal extends WebComponent { return undefined } - const subjects = this.getAuthorizationSubjectEntries(authorization) + const subjects = this.getAuthorizationSubjectEntries(this.getSharedAuthorization(authorization)) if (!subjects.length) { return undefined } @@ -783,8 +791,7 @@ export default class AccessControlModal extends WebComponent { return false } - await this.queuePendingPrinciples([rawValue], this.addAccessRoleValue, undefined, rawValue) - return true + return this.queuePendingPrinciples([rawValue], this.addAccessRoleValue, undefined, rawValue) } private async commitPrinciplesFromInputSafely (): Promise { @@ -802,7 +809,7 @@ export default class AccessControlModal extends WebComponent { role: AccessRole = this.addAccessRoleValue, preferredLabel?: string, inputSnapshot?: string - ) { + ): Promise { const snapshot = inputSnapshot ?? this.principalInputValue.trim() const pendingGrantResults = await Promise.all(principles.map(async principle => this.createPendingAccessGrant(principle, role, preferredLabel))) const newGrants = pendingGrantResults.flatMap(result => ('grant' in result ? [result.grant] : [])) @@ -811,17 +818,25 @@ export default class AccessControlModal extends WebComponent { .map(result => `${result.principle} (${result.error})`) if (!newGrants.length) { + if (failedGrants.length) { + this.failed = true + } this.logPendingGrantFailures(failedGrants, false) - return + return false } const merged = [...this.pendingAccessGrants, ...newGrants] this.pendingAccessGrants = this.dedupePendingAccessGrants(merged) + if (failedGrants.length) { + this.failed = true + } this.logPendingGrantFailures(failedGrants, true) - if (this.principalInputValue.trim() === snapshot) { + if (!failedGrants.length && this.principalInputValue.trim() === snapshot) { this.principalInputValue = '' } + + return failedGrants.length === 0 } private async createPendingAccessGrant ( diff --git a/src/components/access-control-modal/index.ts b/src/components/access-control-modal/index.ts index b8513f270..8b0b7eacd 100644 --- a/src/components/access-control-modal/index.ts +++ b/src/components/access-control-modal/index.ts @@ -1,4 +1,4 @@ import AccessControlModal from './AccessControlModal' export { AccessControlModal } -export default AccessControlModal \ No newline at end of file +export default AccessControlModal diff --git a/test/components/access-control-modal.test.ts b/test/components/access-control-modal.test.ts index 294bba59f..3f7928999 100644 --- a/test/components/access-control-modal.test.ts +++ b/test/components/access-control-modal.test.ts @@ -384,6 +384,64 @@ describe('AccessControlModal submit', () => { document.body.removeChild(element) }) + it.each([ + 'http://www.w3.org/ns/auth/acl#AuthenticatedAgent', + 'http://xmlns.com/foaf/0.1/Agent' + ])('retains non-general subjects from an authorization containing %s', async (generalClass) => { + const element = document.createElement('solid-ui-access-control-modal') as any + const agent = 'https://alice.example.com/profile/card.ttl#me' + const group = 'https://example.com/group' + const agentClass = 'https://example.com/team' + element.subjectUri = 'https://example.com/resource.ttl' + element.accessGrants = [{ + agent: [agent], + agentGroup: [group], + agentClass: [generalClass, agentClass], + mode: ['Read'] + }] + + document.body.appendChild(element) + await element.updateComplete + await element.refreshAccessGrantLabels() + await element.updateComplete + + const expectedLabel = element.getAuthorizationSubjectLabel({ + agent: [agent], + agentGroup: [group], + agentClass: [agentClass], + mode: ['Read'] + }) + const entries = element.getSharedAccessGrantEntries() + expect(entries).toHaveLength(1) + expect(entries[0].authorization.agentClass).toEqual([agentClass]) + expect(entries[0].subjectLabel).toBe(expectedLabel) + expect(element.accessGrants[0].agentClass).toEqual([generalClass, agentClass]) + expect(element.shadowRoot.querySelector('.access-grants-list h3').textContent.trim()).toBe(expectedLabel) + const searchOptions = [...element.shadowRoot.querySelectorAll('solid-ui-combobox.access-grants-search-input solid-ui-combobox-option')] + .map((option: Element) => option.textContent?.trim()) + expect(searchOptions).toEqual([expectedLabel]) + + element.searchValue = 'card.ttl' + await element.updateComplete + expect(element.shadowRoot.querySelector('.access-grants-list h3').textContent.trim()).toBe(expectedLabel) + + const roleSelect = element.shadowRoot.querySelector('.access-grants-list solid-ui-combobox') + roleSelect.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true, + detail: { option: { value: 'Editor', label: 'Editor' } } + })) + await element.updateComplete + await element.onSaveClick() + + expect(planGrant.mock.calls.map(([, subject]) => subject)).toEqual([ + { type: 'agent', iri: agent }, + { type: 'agentGroup', iri: group }, + { type: 'agentClass', iri: agentClass } + ]) + document.body.removeChild(element) + }) + it('persists a changed shared access grant on save', async () => { const element = document.createElement('solid-ui-access-control-modal') as any element.subjectUri = 'https://example.com/resource.ttl' @@ -415,6 +473,26 @@ describe('AccessControlModal submit', () => { document.body.removeChild(element) }) + it('aborts saving when principal lookup fails', async () => { + const element = document.createElement('solid-ui-access-control-modal') as any + element.subjectUri = 'https://example.com/resource.ttl' + element.principalInputValue = 'not-a-principal' + element.publicAccessRoleValue = 'Viewer' + + document.body.appendChild(element) + await element.updateComplete + + await element.onSaveClick() + + expect(planGrant).not.toHaveBeenCalled() + expect(planRevoke).not.toHaveBeenCalled() + expect(applyPlan).not.toHaveBeenCalled() + expect(element.principalInputValue).toBe('not-a-principal') + expect(element.failed).toBe(true) + + document.body.removeChild(element) + }) + it('applies a shared row change to every subject represented by the authorization', async () => { const element = document.createElement('solid-ui-access-control-modal') as any element.subjectUri = 'https://example.com/resource.ttl' From 885b37d7d2bc946bc38e10a2cdb773d912dcbebd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Oct 2026 10:25:27 +0000 Subject: [PATCH 6/7] chore: update solidos dependencies (dev: solid-logic@6.0.1-0 pane-registry@5.0.2-0) (latest: rdflib@2.4.1) --- package-lock.json | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index eef92a3ff..25c0485cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6732,9 +6732,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.27", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz", - "integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==", + "version": "2.11.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz", + "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7109,15 +7109,15 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz", - "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.6.tgz", + "integrity": "sha512-qgICXXFp2/3nAebRLmxcINKEXM1mLK3ij/y31P02VTs/KIkddIeC05n73DxOXsXMB9LFSqNU1/na3lDlOhcbIw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "5.6.2", "rxjs": "7.8.2", - "shell-quote": "1.9.0", + "shell-quote": "1.12.0", "supports-color": "10.2.2", "tree-kill": "1.2.2", "yargs": "18.0.0" @@ -7542,9 +7542,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.451", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.451.tgz", - "integrity": "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg==", + "version": "1.5.452", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz", + "integrity": "sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==", "dev": true, "license": "ISC" }, @@ -11067,9 +11067,9 @@ "license": "MIT" }, "node_modules/n3": { - "version": "2.13.10", - "resolved": "https://registry.npmjs.org/n3/-/n3-2.13.10.tgz", - "integrity": "sha512-GunqYLhMwtrLbpSYpE+7sZyH8A7/7KCpgL4KAGD4RDn0rAiZGxLkyskdAlZGVyJcpwlsjeHJRlFFSUJKHpeOjA==", + "version": "2.13.11", + "resolved": "https://registry.npmjs.org/n3/-/n3-2.13.11.tgz", + "integrity": "sha512-RZ+iINCXqZm4Tr6rA1/WQI573hPrwbkiWQspz5TViIWLWEs5y6+AVWoNQ7preieLhqdi9H1E+AOR+LvlhKxsOg==", "license": "MIT", "dependencies": { "buffer": "^6.0.3", @@ -12638,9 +12638,9 @@ } }, "node_modules/shell-quote": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", - "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "dev": true, "license": "MIT", "engines": { @@ -13907,15 +13907,15 @@ } }, "node_modules/vite": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.3.tgz", - "integrity": "sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.28", - "rolldown": "~1.2.11", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "bin": { From c013c627142f932be72628c63f3215a219648b07 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sat, 10 Oct 2026 05:40:25 +1100 Subject: [PATCH 7/7] new solid-logic version --- package-lock.json | 32 ++++++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25c0485cc..b99773310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "mime-types": "^3.0.2", "pane-registry": "5.0.2-0", "rdflib": "2.4.1", - "solid-logic": "6.0.1-0", + "solid-logic": "6.0.1-1", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" }, @@ -2343,9 +2343,9 @@ } }, "node_modules/@dokieli/web-access-control": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@dokieli/web-access-control/-/web-access-control-1.0.0.tgz", - "integrity": "sha512-KEmpqbEtGTxk2+YUK69GHWwgO5c34foOF6AClTfvqk3O9r5RaEAYa+L0sRaOLfWs1eCXt/quqqFpFzEwC7IP6Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@dokieli/web-access-control/-/web-access-control-1.1.0.tgz", + "integrity": "sha512-LId2cNz0pKafc6dwohaX6j+mr886uEV2o8L0VrAWUDxU0LPN4+8Yc8Yx3D94zrsRSJuBWRtQzSjgtEqhYg5y/w==", "license": "Apache-2.0", "dependencies": { "@rdfjs/data-model": "^2.1.1", @@ -11704,6 +11704,22 @@ "solid-logic": "6.0.1-0" } }, + "node_modules/pane-registry/node_modules/solid-logic": { + "version": "6.0.1-0", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-0.tgz", + "integrity": "sha512-ZEJ4aRK5Q16sYoNTiH8aJ2ctvpZPwvsPYKawL9pmPC7qvF6wVnOucuNLp1EHHYhdX1Wkp9a25mp/my1IsQgofA==", + "license": "MIT", + "dependencies": { + "@dokieli/web-access-control": "^1.0.0", + "@uvdsl/solid-oidc-client-browser": "^0.2.3", + "solid-namespace": "^0.5.4", + "vite": "^8.0.16", + "vitest": "^4.1.9" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12743,12 +12759,12 @@ } }, "node_modules/solid-logic": { - "version": "6.0.1-0", - "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-0.tgz", - "integrity": "sha512-ZEJ4aRK5Q16sYoNTiH8aJ2ctvpZPwvsPYKawL9pmPC7qvF6wVnOucuNLp1EHHYhdX1Wkp9a25mp/my1IsQgofA==", + "version": "6.0.1-1", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-1.tgz", + "integrity": "sha512-k0EB26f3whZWfvePl6qHcHhOa4TWfnjPLphAa++s2ZR7+FvruM9Cc9eyvzJqEF6b3HF72jH8V7ENiRseFI51Ow==", "license": "MIT", "dependencies": { - "@dokieli/web-access-control": "^1.0.0", + "@dokieli/web-access-control": "^1.1.0", "@uvdsl/solid-oidc-client-browser": "^0.2.3", "solid-namespace": "^0.5.4", "vite": "^8.0.16", diff --git a/package.json b/package.json index 450556207..5b0b8d37f 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "mime-types": "^3.0.2", "pane-registry": "5.0.2-0", "rdflib": "2.4.1", - "solid-logic": "6.0.1-0", + "solid-logic": "6.0.1-1", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" },