Repository navigation
feat(config): set instance Launchpad defaults in linkforty.config.json - #90
Merged
Merged
Conversation
The Launchpad page was configurable only through an organization's settings.launchpad. Links created through Core's own API have no organization, so a self-hosted instance could not set its app name, icon, website or colors, or turn on the desktop "always" and mobile "page" modes, without editing the database by hand. linkforty.config.json now takes a launchpad section with the same fields (desktop, mobile, appName, appIconUrl, appWebsiteUrl, accentColor, backgroundColor), validated strictly at startup like notFound: an invalid value or unknown setting stops the server with the setting named. Colors must be six-digit hex because the page mixes them. templateId is not accepted there, since it names a host-rendered template and only makes sense per organization. An organization's settings.launchpad overrides the instance's field by field (mergeLaunchpadSettings), not as a whole. Organizations routinely set one or two fields, and replacing the instance's settings would drop its app name and icon for them. An organization value that fails validation is ignored rather than erasing a valid instance value. A link's own launchpad_mode still wins over both. This differs from notFound, where an organization's settings replace the instance's, and the docs say so. createServer() passes config.launchpad to the redirect routes as launchpad.defaults, keeping any resolveContent hook or beaconUrl passed in code; options in code still take precedence over the file. With no config the output is unchanged, which a test checks across desktop and mobile, with and without organization settings. Adds src/index.config.test.ts, which runs createServer() with only the database mocked and checks that both config sections reach the pages a visitor sees. Documents the section in DOCKER.md, the README and linkforty.config.example.json.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The Launchpad page could only be configured through an organization's
settings.launchpad. Links created through Core's own API have no organization, so a self-hosted instance couldn't set its app name, icon, website or colors, or turn on the desktopalwaysand mobilepagemodes, without editing the database by hand.linkforty.config.json, added in 1.42.0, now takes alaunchpadsection. It's the instance's default for every link, including links with no organization. An organization'ssettings.launchpadoverrides it field by field.Type of Change
Configuring it
{ "launchpad": { "desktop": "no-destination", "mobile": "store", "appName": "Example App", "appIconUrl": "https://example.com/icon.png", "appWebsiteUrl": "https://example.com/", "accentColor": "#0f766e", "backgroundColor": "#101418" } }Which value applies, per field: a link's
launchpadModewins over the organization'ssettings.launchpad, which wins over the config file'slaunchpad, which wins over the built-in defaults.Why field by field:
notFoundreplaces as a whole, because it's one small page. Launchpad settings are routinely partial: an organization often sets just a color or a mode. Replacing wholesale would drop the instance's app name and icon for those organizations. Merging per field keeps them. An organization value that fails validation is ignored rather than erasing a valid instance value. The docs spell out the difference fromnotFound.Changes Made
src/lib/config.ts: a strictlaunchpadschema with the same limits Core already applies to organization settings: mode enums,appNameup to 60 characters, http(s) URLs, and six-digit hex colors (the page mixes them to build its palette).templateIdis rejected: it names a host-rendered template and only makes sense per organization.notFound.src/lib/launchpad.ts:mergeLaunchpadSettings(defaults, orgSettings)is the single place the merge happens.src/routes/redirect.ts:RedirectRouteOptions.launchpad.defaults. Both places that read the Launchpad settings (desktop and mobile) use the merged settings, andresolveContentreceives them.src/index.ts:createServer()passesconfig.launchpadaslaunchpad.defaults, keeping anyresolveContent/beaconUrlpassed in code. Options in code still take precedence over the file.launchpadblock inlinkforty.config.example.json.Testing
npm test): 628 passed. The database integration suites skip withoutDATABASE_URL.npm run typecheck)Test Coverage
src/routes/redirect.launchpad.test.ts:desktop: "always"andmobile: "page"serve the page;appNameoverrides it while the instance'saccentColorstays;offand an organization'soffboth still win over an instancealways.src/lib/launchpad.test.ts:mergeLaunchpadSettingsequalsreadLaunchpadSettingswhen there are no defaults, merges per field, and invalid organization values never erase valid defaults.src/lib/config.test.ts: every setting parses. A bad mode, a three-digit hex, a non-http(s) icon,templateId, an unknown key and an over-long name are each rejected with the setting named.src/index.config.test.ts: runscreateServer()with only the database mocked. It checks that both config sections reach the pages a visitor sees, that hooks passed in code are kept, that code options win over the file, and that nothing changes without a config. This also covers thenotFoundwiring from 1.42.0."launchpad": { "accentColor": "teal" }exits 1 withlaunchpad.accentColor: must be a six-digit hex color such as #0f766e.npm testChecklist
feat:,fix:)Breaking Changes
None. Without a
launchpadsection the page behaves exactly as before, and a test checks that. Organization settings keep working unchanged.