the store's README shows in the platform, not just on the website
A plugin's page in the Marketplace screen now renders the README the marketplace holds — the same text the public site shows, in the pane where somebody actually decides whether to install it. Fetched by the SERVER and proxied, though CORS would have allowed the browser to ask directly. An Officer client is routinely somewhere its server is not: a phone on the tailnet, a laptop with the store blocked. The server already had to reach the marketplace for the catalogue, so routing through it means the README appears in exactly the cases the plugin list did — rather than in a strictly smaller set, which is the kind of difference nobody reports and everybody notices. Separate request from the list, because the list endpoint deliberately carries no READMEs: three of them is ~38KB of markdown on the request that draws the tiles. This one fires when a plugin is opened. Absence renders nothing at all — no error, no empty state. A plugin without a README is normal and a store that is down must not put a failure message on a page whose job is installing things. Under `/plugins` so the existing permission covers it: reading the store and installing from it are the same privilege, and a second key would be a second thing to grant for no separate decision. tsgo clean, frontend builds, 873 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { resolveIcon } from '../../utils/resolve-icon';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import {
|
||||
usePlugins,
|
||||
type AvailablePlugin,
|
||||
@@ -234,9 +238,44 @@ const AvailableDetail = ({
|
||||
</div>
|
||||
|
||||
<ActionLog steps={steps} result={result} running={running} />
|
||||
|
||||
<Readme appName={plugin.appName} />
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* What the marketplace says about a plugin, in the pane where somebody decides whether to install it.
|
||||
*
|
||||
* Fetched separately from the plugin list on purpose: the list draws tiles and three READMEs is ~38KB of
|
||||
* markdown that nobody has asked to read yet. This request happens when a plugin is actually opened.
|
||||
*
|
||||
* Absence renders NOTHING — no error, no empty state, no spinner left behind. A plugin without a README
|
||||
* is normal, and the store being unreachable must not put a failure message on a page whose job is
|
||||
* installing things. The list itself already degrades; this follows it.
|
||||
*
|
||||
* Styled with `chat-md` — the app's one markdown stylesheet (`styles/prose.css`) — rather than a new
|
||||
* class. The same content should not have two looks in the same application.
|
||||
*/
|
||||
const Readme = ({ appName }: { appName: string }) => {
|
||||
const { get } = useClient();
|
||||
const { data } = useQuery({
|
||||
queryKey: ['plugins', 'readme', appName],
|
||||
queryFn: () => get<{ readme: string | null }>(`/plugins/catalogue/${appName}/readme`),
|
||||
// The store caches for five minutes server-side; re-reading it on every tab focus would be a
|
||||
// network round trip to redraw identical markdown.
|
||||
staleTime: 5 * 60 * 1000,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
if (!data?.readme) return null;
|
||||
|
||||
return (
|
||||
<section className="chat-md mt-8 border-t border-duck-dark/10 pt-6">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{data.readme}</ReactMarkdown>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const PluginDetail = () => {
|
||||
const { plugins, available, steps, result, running, run } = usePlugins();
|
||||
const [params] = useSearchParams();
|
||||
|
||||
Reference in New Issue
Block a user