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
@@ -3,6 +3,7 @@ import type { Ref } from 'vue'
import type { NodeState, StageNode } from '../types/node'
import useCategoriesStage from './stages/categories'
import useDescriptionStage from './stages/description'
import useDisclosuresStage from './stages/disclosures'
import useGalleryStage from './stages/gallery'
import useLicenseStage from './stages/license'
import useLinksStage from './stages/links'
@@ -29,6 +30,7 @@ export function useStages(
useSummaryStage(),
useDescriptionStage(),
useGalleryStage(),
useDisclosuresStage(),
useLinksStage(),
useLicenseStage(),
useCategoriesStage(),
@@ -0,0 +1 @@
- Advertisements or sponsored promotions of outside content.
@@ -0,0 +1,2 @@
- AI-generated or assisted content. </br>
%R6.1%, proper disclosure of AI usage is strictly required.
@@ -0,0 +1 @@
- AI-generated or assisted art, assets, or other design elements.
@@ -0,0 +1 @@
- AI-generated or assisted code.
@@ -0,0 +1 @@
- Functionality that relies on or is designed by generative AI.
@@ -0,0 +1 @@
- AI-generated or assisted writing or text, including translations.
@@ -0,0 +1,2 @@
</br>
Don't forget to label the inclusion of:
@@ -0,0 +1 @@
- Being archived, or otherwise no longer receiving updates.
@@ -0,0 +1,2 @@
- Derivative content, such as code or assets from another project or creator. </br>
%R4%, you may be required to list the origin of content you did not create here if the content's license requires attribution.
@@ -0,0 +1,2 @@
</br>
Currently, your %PROJECT_TYPE_FORMATTED_LOWER% appears to be missing disclosure of:
@@ -0,0 +1,3 @@
## Missing Content Disclosures
%R5.9%, we ask that you ensure all applicable %PROJECT_CONTENT_DISCLOSURES_FLINK% are filled out with accurate information.
@@ -0,0 +1 @@
- Features locked behind a paywall, donation, or other monetary exchange.
@@ -0,0 +1 @@
- Content that may be dangerous to users with photosensitive epilepsy.
@@ -0,0 +1 @@
- Features that interact with the user's machine outside of the context of the game.
@@ -0,0 +1,2 @@
- Telemetry or any other instances of data being sent to a remote server that the user did not connect to. </br>
%R1.11%, proper disclosure of data collection is strictly required.
@@ -0,0 +1,2 @@
</br>
Additionally, it's important your telemetry's consent model is accurate. In this case, your %PROJECT_TYPE_FORMATTED_LOWER%'s telemetry appears to be "Always active".
@@ -0,0 +1,2 @@
</br>
Additionally, it's important your telemetry's consent model is accurate. In this case, your %PROJECT_TYPE_FORMATTED_LOWER%'s telemetry appears to be "Opt In".
@@ -0,0 +1,2 @@
</br>
Additionally, it's important your telemetry's consent model is accurate. In this case, your %PROJECT_TYPE_FORMATTED_LOWER%'s telemetry appears to be "Opt Out".
@@ -5,6 +5,8 @@
Currently, it looks like some images do not accurately represent the content of your %PROJECT_TYPE_FORMATTED_LOWER%.
Please make sure you use authentic images such as in-game screenshots when showcasing the content or functionality of your work.
Images such as renders, AI generated or assisted images, or heavily modified images should not be used to demonstrate the functionality of your project in the %PROJECT_DESCRIPTION_FLINK% or %PROJECT_GALLERY_FLINK%.
Images such as renders, or heavily modified images should not be used to demonstrate the functionality of your project in the %PROJECT_DESCRIPTION_FLINK% or %PROJECT_GALLERY_FLINK%.
You may still include these types of branding assets or unrelated images on your project page, so long as it is clear they are not authentic representations of the content.
Per section 6.2 of %RULES%, the use of AI images is strictly prohibited.
@@ -3,4 +3,5 @@
%R4%, it is important that your project your project abides by the license of and is significantly divergent from the source work.
Unfortunately, this project does not appear to meet these requirements. \
For your project to be published, you can instead provide proof of your explicit permission to distribute this project from the creator(s) of the source work.
For your project to be published, you can instead provide proof of your explicit permission to distribute this project from the creator(s) of the source work. \
Remember to appropriately credit all external content you've included or forked in your project's derivative content %PROJECT_CONTENT_DISCLOSURES_FLINK%, this may be required by the license of the source work.
@@ -0,0 +1,4 @@
## Missing Attribution
Please ensure you've provided proper attribution to the source of all assets you have not created yourself by listing them accordingly in your project's %PROJECT_CONTENT_DISCLOSURES_FLINK%.
Per section 4 of %RULES%, this is strictly required for any content licensed with an attribution requirement clause.
@@ -1,5 +1,7 @@
## Reuploads are forbidden
This server appears to have uploaded a Modpack by another creator. \
This server appears to have uploaded a Modpack by another creator.
%R4%, we ask that you provide proof of your permission to distribute this pack on Modrinth. \
Either implicit permission abiding by the terms of the content's license(s) or explicit permission from the original creator of the pack.
Either implicit permission abiding by the terms of the content's license(s) or explicit permission from the original creator of the pack. \
Please remember to appropriately credit all external content you've included or forked in your project's derivative content %PROJECT_CONTENT_DISCLOSURES_FLINK%.
@@ -2,4 +2,5 @@
This project appears to contain content from other creators. \
%R4%, we ask that you provide proof of your permission to distribute this content, or derivatives of this content in your project on Modrinth. \
Either implicit permission abiding by the terms of the content's license(s) or explicit permission from the original creator of the content.
Either implicit permission abiding by the terms of the content's license(s) or explicit permission from the original creator of the content. \
Please remember to appropriately credit all external content you've included or forked in your project's derivative content %PROJECT_CONTENT_DISCLOSURES_FLINK%, this may be required by the license of the content.
@@ -1,4 +1,5 @@
## Forks and Reuploads
%R4%, please provide proof that this project is both license-abiding and significantly divergent from the source work. \
Alternatively, please provide proof of your explicit permission from the author of the source work to distribute this content on Modrinth.
Alternatively, please provide proof of your explicit permission from the author of the source work to distribute this content on Modrinth. \
Please remember to appropriately credit all external content you've included or forked in your project's derivative content %PROJECT_CONTENT_DISCLOSURES_FLINK%, this may be required by the license of the source work.
@@ -0,0 +1,6 @@
## Project Withheld
%R6.2b%, this project is not suitable to be published on Modrinth.
With this in mind, this project will remain unlisted, so you may still share it privately with a direct link. \
We appreciate your understanding and ask that you **do not resubmit**.
@@ -1,6 +1,6 @@
## Unsupported Project
Modrinth does not support uploading multiple variations of your project as separate versions.
New Versions of your project should strictly be a linear upgrade of the same content.
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want.
%R5.8%, Modrinth does not support uploading multiple variations of your project as separate versions. \
New Versions of your project should strictly be a linear upgrade of the same content. \
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want. \
We ask that you upload each alternate version of your project as a new project, ensuring that all users will be able to access and easily find your content.
@@ -1,6 +1,6 @@
## Unsupported Project
Modrinth does not support uploading multiple variations of your project as separate versions.
New Versions of your project should strictly be a linear upgrade of the same content.
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want.
%R5.8%, Modrinth does not support uploading multiple variations of your project as separate versions. \
New Versions of your project should strictly be a linear upgrade of the same content. \
Having alternate versions of your content on the same project will hurt the functionality of the Modrinth App and other supported launchers as it would prevent users from updating your content, and may make it harder for your users to find the content they want. \
To ensure a smooth experience for you and your users, please upload each alternate version of your modpack as its own Modpack project, thank you.
@@ -1,4 +1,4 @@
## Vanilla Assets
Your resource pack currently includes an excessive amount of unmodified assets from vanilla Minecraft.
Your %PROJECT_TYPES_FORMATTED_LOWER% currently includes an excessive amount of unmodified assets from vanilla Minecraft. \
Please remove these from your pack and ensure your project only contains original assets created by you, thank you.
+29
View File
@@ -282,4 +282,33 @@ export const coreNags: Nag[] = [
context.currentRoute !== 'type-project-settings-permissions',
},
},
{
id: 'check-disclosures',
title: defineMessage({
id: 'nags.check-disclosures.title',
defaultMessage: 'Check content disclosures',
}),
description: (context: NagContext) => {
const { formatMessage } = useVIntl()
return formatMessage(
defineMessage({
id: 'nags.check-disclosures.description',
defaultMessage:
'Make sure users are aware of any important details by filling in content disclosures that apply to your {type}.',
}),
{ type: formatProjectTypeSentence(formatMessage, context.project.project_type) },
)
},
status: 'suggestion',
shouldShow: () => true,
link: {
path: 'settings/disclosures',
title: defineMessage({
id: 'nags.settings.disclosures.title',
defaultMessage: 'Visit disclosure settings',
}),
shouldShow: (context: NagContext) =>
context.currentRoute !== 'type-project-settings-disclosures',
},
},
]
@@ -0,0 +1,76 @@
import { TriangleAlertIcon } from '@modrinth/assets'
import { group, stage, toggle } from '../../types/node'
export default function () {
return stage('disclosures', 'Disclosures')
.hint('Has this project selected all proper content disclosures?')
.guidance('https://www.notion.so/2e15ee711bf080e4a41df61bbab49892')
.icon(TriangleAlertIcon)
.navigate('/settings/disclosures')
.children(
group().children(
toggle('missing-disclosures', 'Disclosures Missing')
.message('missing-disclosures/missing-disclosures-header')
.children(
group()
.title('Which content disclosures are missing?')
.children(
toggle('ai', 'AI Usage')
.suggestedStatus('flagged')
.message()
.children(
group()
.title('What kind of AI content is present?')
.children(
toggle('code', 'Code').message('ai-usages/code'),
toggle('assets', 'Assets').message('ai-usages/assets'),
toggle('text', 'Text').message('ai-usages/text'),
toggle('functionality', 'Functionality').message('ai-usages/functionality'),
),
)
.collect(undefined, 'ai/list-intro'),
toggle('ads', 'Advertisements').suggestedStatus('flagged').message(),
toggle('paid-features', 'Paid Features').suggestedStatus('flagged').message(),
toggle('telemetry', 'Telemetry')
.suggestedStatus('rejected')
.message()
.children(
group()
.title("What is the telemetry's consent model?")
.children(
toggle('opt-in', 'Opt In')
.message('consent-model/opt-in')
.enabled((state) => !state?.['opt-out'] && !state?.['always']),
toggle('opt-out', 'Opt Out')
.message('consent-model/opt-out')
.enabled((state) => !state?.['opt-in'] && !state?.['always']),
toggle('always', 'Always Online')
.message('consent-model/always')
.enabled((state) => !state?.['opt-out'] && !state?.['opt-in']),
),
)
.collect(),
toggle('derivative-content', 'Derivative Content')
.suggestedStatus('rejected')
.message(),
toggle('photosensitivity', 'Photosensitivity')
.suggestedStatus('rejected')
.message(),
toggle('system-interactions', 'System Interactions')
.suggestedStatus('rejected')
.message(),
toggle('archive', 'Archive').message(),
),
)
.collect(undefined, 'missing-disclosures/list-intro'),
),
)
}
@@ -88,6 +88,13 @@ export default function () {
OVERRIDES: state.overrides,
}))
.children(markdown('overrides').title('Forbidden overrides list').required()),
toggle('missing-attribution', 'Missing Attribution')
.suggestedStatus('rejected')
.message((state) => ({
MISSING_CREDITS: state.missing_credits,
})),
//.children(markdown('missing_credits').title('Add list of missing credits.')),
),
)
}
@@ -77,6 +77,8 @@ export default function () {
.suggestedStatus('flagged')
.message(),
toggle('ai-generated', 'AI Generated').suggestedStatus('flagged').message(),
toggle('rule-breaking-other', 'Other')
.suggestedStatus('rejected')
.message((state) => ({ MESSAGE: state.message }))
@@ -1,4 +1,4 @@
import { TriangleAlertIcon } from '@modrinth/assets'
import { MegaphoneIcon } from '@modrinth/assets'
import { injectProjectPageContext } from '@modrinth/ui'
import type { Ref } from 'vue'
import { computed } from 'vue'
@@ -28,7 +28,7 @@ export default function (
.guidance(
'https://www.notion.so/2e15ee711bf080e4a41df61bbab49892#2e35ee711bf080968699c397e470eca6',
)
.icon(TriangleAlertIcon)
.icon(MegaphoneIcon)
.navigate('/moderation')
.children(
() => (
@@ -47,6 +47,12 @@
"nags.all-tags-selected.title": {
"defaultMessage": "Select accurate tags"
},
"nags.check-disclosures.description": {
"defaultMessage": "Make sure users are aware of any important details by filling in content disclosures that apply to your {type}."
},
"nags.check-disclosures.title": {
"defaultMessage": "Check content disclosures"
},
"nags.description-too-short.description": {
"defaultMessage": "Your description is {length, plural, one {# readable character} other {# readable characters}}. At least {minChars, plural, one {# character} other {# characters}} is recommended to create a clear and informative description."
},
@@ -194,6 +200,9 @@
"nags.settings.description.title": {
"defaultMessage": "Visit description settings"
},
"nags.settings.disclosures.title": {
"defaultMessage": "Visit disclosure settings"
},
"nags.settings.license.title": {
"defaultMessage": "Visit license settings"
},
@@ -108,7 +108,7 @@ export interface Messageable {
this: this,
content: string | ((state: Record<string, NodeState>) => string | Promise<string>),
): this
collect(this: this, path?: string | (() => string) | GetVarsFn, getVars?: GetVarsFn): this
collect(this: this, fallback?: string | (() => string), prefix?: string | (() => string)): this
}
function splitPathAndVars(
@@ -151,17 +151,18 @@ export function withMessaging<T extends object>(node: T): T & Messageable {
},
collect(
this: Messageable,
pathOrGetVars?: string | (() => string) | GetVarsFn,
getVarsArg?: GetVarsFn,
fallbackPath?: string | (() => string),
prefixPath?: string | (() => string),
) {
const { path, getVars } = splitPathAndVars(pathOrGetVars, getVarsArg)
const fallback: MessageSegment | undefined =
path !== undefined || getVars !== undefined
? path === undefined
? { type: 'auto', ...(getVars && { getVars }) }
: { type: 'path', path, ...(getVars && { getVars }) }
: undefined
this._segments.push({ type: 'collect', ...(fallback && { fallback }) })
fallbackPath !== undefined ? { type: 'path', path: fallbackPath } : undefined
const prefix: MessageSegment | undefined =
prefixPath !== undefined ? { type: 'path', path: prefixPath } : undefined
this._segments.push({
type: 'collect',
...(fallback && { fallback }),
...(prefix && { prefix }),
})
return this
},
})
@@ -87,6 +87,8 @@ export async function evalActiveAction(
}
if (!collected.trim() && seg.fallback) {
collected = await evalSegment(seg.fallback, entry.state, entry.statePath)
} else if (collected.trim() && seg.prefix) {
collected = (await evalSegment(seg.prefix, entry.state, entry.statePath)) + collected
}
result += collected
} else {
+1 -1
View File
@@ -30,4 +30,4 @@ export type MessageSegment =
| { type: 'fn'; fn: ContentFn }
| { type: 'auto'; getVars?: GetVarsFn }
| { type: 'path'; path: string | (() => string); getVars?: GetVarsFn }
| { type: 'collect'; fallback?: MessageSegment }
| { type: 'collect'; fallback?: MessageSegment; prefix?: MessageSegment }
+23 -1
View File
@@ -151,11 +151,29 @@ export function flattenStaticVariables(): Record<string, string> {
`Per section 4 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#copyright-and-legality-of-content)`
vars[`R5`] =
`Per section 5 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous)`
const rule5subs = 8
const rule5subs = 9
for (let n = 1; n <= rule5subs; n++) {
vars[`R5.${n}`] =
`Per section 5.${n} of [Modrinth's Content Rules](https://modrinth.com/legal/rules#miscellaneous)`
}
vars[`R6`] =
`Per section 6 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#generative-ai)`
vars[`R6.1`] =
`Per section 6.1 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#disclosure-of-ai-generated-content)`
const rule6sub1subs = 2
for (let n = 1; n <= rule6sub1subs; n++) {
const l = String.fromCharCode(96 + n)
vars[`R6.1${l}`] =
`Per section 6.1${l} of [Modrinth's Content Rules](https://modrinth.com/legal/rules#disclosure-of-ai-generated-content)`
}
vars[`R6.2`] =
`Per section 6.2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules#prohibited-usage-of-ai)`
const rule6sub2subs = 4
for (let n = 1; n <= rule6sub2subs; n++) {
const l = String.fromCharCode(96 + n)
vars[`R6.2${l}`] =
`Per section 6.2${l} of [Modrinth's Content Rules](https://modrinth.com/legal/rules#prohibited-usage-of-ai)`
}
vars[`TOS`] = `[Terms of Use](https://modrinth.com/legal/terms)`
vars[`COPYRIGHT_POLICY`] = `[Copyright Policy](https://modrinth.com/legal/copyright)`
vars[`SUPPORT`] =
@@ -287,6 +305,10 @@ export function flattenProjectVariables(
vars[`PROJECT_MONETIZATION_SETTINGS_LINK`] = `https://modrinth.com/project/${project.id}/settings`
vars[`PROJECT_MONETIZATION_SETTINGS_FLINK`] =
`[Monetization settings](https://modrinth.com/project/${project.id}/settings)`
vars[`PROJECT_CONTENT_DISCLOSURES_LINK`] =
`https://modrinth.com/project/${project.id}/settings/disclosures`
vars[`PROJECT_CONTENT_DISCLOSURES_FLINK`] =
`[Content Disclosures](https://modrinth.com/project/${project.id}/settings/disclosures)`
return vars
}