project disclosures frontend (#6955)

* begin project disclosures

* new project settings header

* begin disclosure settings, edit content rules

* togglecards

* update phrasing of the rules, prepr

* more disclosure settings structuring

* add functionality toggle

* implement functionality with staging api

* improve project settings head titles

* feat(labrinth): project disclosures model

* feat(labrinth): project disclosures database model

* feat(labrinth): project disclosures get endpoint

* feat(labrinth): censor user ids if set by moderator

* feat(labrinth): wrap disclosures in struct

* feat(labrinth): edit project disclosures endpoint

* style(labrinth): cargo fmt

* style(labrinth): fix typo

* feat(labrinth): add fields for ai content disclosure

* fix(labrinth): field typo

* chore(labrinth): update query cache

* feat(labrinth): index disclosures in search

* refactor(labrinth): use enum instead of bools

* feat(labrinth): trigger incremental index on disclosure change

* feat(labrinth): change archived status to disclosure

* feat(labrinth): use disclosure for archival status

* fix(labrinth): error type for deserialization

* fix(labrinth): migration timestamp

* fix(labrinth): add disclosures to elasticsearch schema

* disclosures in search

* update photosensitivity warning copy

* update archiving, move general project settings to v3 (start redesign there), use project id lookups for stable cache keys, advanced in server search

* blue archive banner

* display AI use types

* improve labels

* add modrinth to link

* add placeholder missing disclosure report type

* add AI metadata checking to block image uploads with notice

* update copy

* update section 4 of rules

* Update rule 6 layout

* fix(labrinth): don't remove archival disclosure when changing status via v3 api

* feat(labrinth): derive str for ai usages and telemtry consent

* feat(labrinth): include ai usages and telemetry consent in disclosure types

* Update moderation checklist (#7056)

* fix: missing delphi severity (#7054)

* Utils nav, new disclosures stage, ai button in rules

* add prefix to collect

* Finish up new disclosures stage

* update r4 messages

* new r4 msg, update showcase clarity message

* fix: version upload failing to detect mrpack loader (#7063)

* fix: mrpack exporting with zip64 (#7064)

* fix: action bar max width (#7048)

* fix: action bar max width

* fix: width

* changelog

* Rule placeholders with subsections & anchors, update messages.

* update new messages to account for new rule and placeholder layout

* Add nag for content disclosures

---------

Co-authored-by: ThatGravyBoat <gravy@thatgravyboat.tech>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>

* prepr

* refactor(labrinth): move disclosure parsing to document creation

* fix(labrinth): prevent removing moderator added archival disclosures

* fix(labrinth): dedupe ai usages

* fix(labrinth): auth logic on archived disclosure removal

* feat(labrinth): add interactions field

* style(labrinth): cargo fmt

* feat(labrinth): granular disclosure locking

* add 5.8, move 5.9, update messages and placeholders accordingly.

* move disclosures stage earlier

* include consent model info in telemetry disclosure msgs

* prepr + update date

* typo

* qa pass

* search suboptions

* add empty state

* checkboxes in column

* persistent advanced filters

* blog draft

* prepr

* support soft deletion

* reload instead of optimistically updating

* update blog

* TOS update + minor verbiage adjustment

* 45 day grace

---------

Co-authored-by: sychic <47618543+Sychic@users.noreply.github.com>
Co-authored-by: coolbot <76798835+coolbot100s@users.noreply.github.com>
Co-authored-by: ThatGravyBoat <gravy@thatgravyboat.tech>
Co-authored-by: chyzman <chyzalt@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
This commit is contained in:
Prospector
2026-08-13 09:01:18 -07:00
committed by GitHub
co-authored by ThatGravyBoat chyzman Truman Gao sychic coolbot
parent 06b7c44dcc
commit 755825b09a
230 changed files with 5802 additions and 1268 deletions
@@ -222,4 +222,116 @@ export class LabrinthProjectsV3Module extends AbstractModule {
method: 'DELETE',
})
}
/**
* Create a gallery image for a project
*
* @param id - Project ID or slug
* @param file - Image file to upload
* @param options - Gallery image options
*
* @example
* ```typescript
* await client.labrinth.projects_v3.createGalleryImage('sodium', imageFile, {
* featured: true,
* name: 'Screenshot 1',
* description: 'Main menu with Sodium enabled'
* })
* ```
*/
public async createGalleryImage(
id: string,
file: Blob,
options: {
ext: string
featured: boolean
name?: string
description?: string
ordering?: number
},
): Promise<void> {
const params: Record<string, string> = {
ext: options.ext,
featured: String(options.featured),
}
if (options.name) params.name = options.name
if (options.description) params.description = options.description
if (options.ordering !== undefined) params.ordering = String(options.ordering)
return this.client.request(`/project/${id}/gallery`, {
api: 'labrinth',
version: 3,
method: 'POST',
params,
body: file,
})
}
/**
* Delete a gallery image from a project
*
* @param id - Project ID or slug
* @param url - URL of the gallery image to delete
*
* @example
* ```typescript
* await client.labrinth.projects_v3.deleteGalleryImage('sodium', 'https://cdn.modrinth.com/...')
* ```
*/
public async deleteGalleryImage(id: string, url: string): Promise<void> {
return this.client.request(`/project/${id}/gallery`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
params: { url },
})
}
/**
* Get content disclosures for a project
*
* @param id - Project ID or slug
* @returns Promise resolving to the project's disclosures
*
* @example
* ```typescript
* const { disclosures } = await client.labrinth.projects_v3.getDisclosures('sodium')
* ```
*/
public async getDisclosures(id: string): Promise<Labrinth.Projects.v3.GetProjectDisclosures> {
return this.client.request<Labrinth.Projects.v3.GetProjectDisclosures>(
`/project/${id}/disclosures`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
/**
* Modify content disclosures for a project
*
* @param id - Project ID or slug
* @param data - Disclosures to set and types to remove
*
* @example
* ```typescript
* await client.labrinth.projects_v3.modifyDisclosures('sodium', {
* set: [{ type: 'ai_content', uses: ['text'], note: 'Translations are AI-generated.' }],
* remove: ['advertisements'],
* })
* ```
*/
public async modifyDisclosures(
id: string,
data: Labrinth.Projects.v3.ModifyProjectDisclosures,
): Promise<void> {
return this.client.request(`/project/${id}/disclosures`, {
api: 'labrinth',
version: 3,
method: 'PATCH',
body: data,
})
}
}
@@ -1301,6 +1301,80 @@ export namespace Labrinth {
projects: Project[]
versions: Labrinth.Versions.v3.Version[]
}
export type TelemetryConsent = 'opt_in' | 'opt_out' | 'always_active'
export type AiUsage = 'code' | 'assets' | 'text' | 'functionality'
export type DisclosureLockStatus = 'unlocked' | 'cannot_disable' | 'fully_locked'
export type DerivativeSource = {
label: string
link?: string | null
note?: string | null
}
export type ProjectDisclosure =
| {
type: 'ai_content'
uses: AiUsage[]
note?: string | null
}
| {
type: 'advertisements'
note?: string | null
}
| {
type: 'epilepsy_triggers'
note?: string | null
}
| {
type: 'system_interactions'
interactions: string[]
note?: string | null
}
| {
type: 'telemetry'
consent: TelemetryConsent
data_collected: string[]
}
| {
type: 'derivative_work'
sources: DerivativeSource[]
}
| {
type: 'paid_features'
features: string[]
}
| {
type: 'archived'
note?: string | null
}
export type ProjectDisclosureData = ProjectDisclosure & {
set_by_moderator: boolean
lock_status: DisclosureLockStatus
updated_at: string
updated_by?: string | null
deleted_at?: string | null
}
export type ProjectDisclosureType = ProjectDisclosure['type']
export type ProjectDisclosureOf<T extends ProjectDisclosureType> = Extract<
ProjectDisclosureData,
{ type: T }
>
export type GetProjectDisclosures = {
disclosures: ProjectDisclosureData[]
}
export type ModifyProjectDisclosures = {
set: ProjectDisclosure[]
remove: ProjectDisclosureType[]
lock_status?: DisclosureLockStatus | null
}
}
}