Skip to the content

Capsid menu

app/routes/procedure.tsx

84 lines and 5 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of procedure.tsx
FunctionLinesComplexityCoveredCRAPMutation
headers32 to 341No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
loader36 to 545No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
meta56 to 643No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
ProcedureRoute66 to 912No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:75>75 to 771No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code

Imports

It imports app/components/breadcrumb.tsx, app/components/enhance.tsx, app/components/page-shell.tsx, app/components/procedure.tsx, app/db/procedure-versions.ts, app/db/procedures.ts, app/kb/procedures/json-ld.mjs, app/kb/procedures/library.mjs, app/kb/procedures/lineage.server.ts, app/kb/procedures/proof.server.ts, app/kb/procedures/route.ts, app/lib/access.server.ts, app/lib/context.ts, app/lib/json-ld.mjs, app/lib/seo.ts. No module of this repo imports it.

Source

import { data } from "react-router";

import { Breadcrumb } from "~/components/breadcrumb";
import { Enhance } from "~/components/enhance";
import { PageShell } from "~/components/page-shell";
import { ProcedureView } from "~/components/procedure";
import { listFrozenVersions } from "~/db/procedure-versions";
import { getProcedureByPath, listPublishedLibraryRecords } from "~/db/procedures";
import { isAdminViewer } from "~/lib/access.server";
import { getEnv } from "~/lib/context";
import { jsonLd as serializeJsonLd } from "~/lib/json-ld.mjs";
import { procedureJsonLd } from "~/kb/procedures/json-ld.mjs";
import { lineageFor } from "~/kb/procedures/lineage.server";
import { libraryItems, workflowContext } from "~/kb/procedures/library.mjs";
import { proofFor } from "~/kb/procedures/proof.server";
import { procedureTrail, PROCEDURES_CACHE_TAG, readScale } from "~/kb/procedures/route";
import { SITE, SITE_ORIGIN, breadcrumbJsonLd, pageMeta, personId, publicHtmlHeaders } from "~/lib/seo";

import type { Route } from "./+types/procedure";

// prose.css is route-scoped: a page using `.prose` without importing it renders unstyled.
import "~/styles/prose.css";
import "~/styles/procedure.css";
import "~/styles/run.css";

/**
 * Every procedure page (docs/PROCEDURES.md): a protocol, a recipe or a computational procedure, drawn
 * from its D1 row at request time, so an edit saved through the operator API is live without a deploy.
 * A draft is served only to the signed-in admin; that request carries a cookie, and the Worker never
 * stores a cookie-bearing response.
 */
export function headers() {
  return new Headers(publicHtmlHeaders(PROCEDURES_CACHE_TAG));
}

export async function loader({ request, context }: Route.LoaderArgs) {
  const env = getEnv(context);
  const url = new URL(request.url);
  const path = url.pathname.replace(/\/+$/, "");
  const row = await getProcedureByPath(env, path);
  if (!row) throw data(null, { status: 404 });
  if (row.status === "draft" && !(await isAdminViewer(env, request))) throw data(null, { status: 404 });
  const { count, factor, units } = readScale(row.record, url);
  // Where a protocol sits in the phage workflow: its neighbours are the other published protocols, so they are read here.
  const workflow =
    row.record.profile === "protocol" ? workflowContext(libraryItems(await listPublishedLibraryRecords(env)), row.record.path) : null;
  // Proof of use is slugs in the file; the papers' and phages' words and addresses are read from their rows.
  const proof = await proofFor(env, row.record.proofOfUse);
  // The versions with a frozen copy, which the history links.
  const frozen = await listFrozenVersions(env, row.slug);
  // A variant's parent: its title and the frozen copy it was taken from are the parent's own facts (P3).
  const lineage = await lineageFor(env, row.record);
  return { record: row.record, draft: row.status === "draft", count, factor, units, workflow, proof, frozen, lineage };
}

export function meta({ loaderData }: Route.MetaArgs) {
  if (!loaderData) return [];
  const { record, draft } = loaderData;
  return [
    ...pageMeta({ title: record.seoTitle, description: record.description, path: record.path }),
    { tagName: "link", rel: "alternate", type: "text/markdown", href: `${SITE_ORIGIN}${record.path}.md` },
    ...(draft ? [{ name: "robots", content: "noindex" }] : []),
  ];
}

export default function ProcedureRoute({ loaderData }: Route.ComponentProps) {
  const { record, draft, count, factor, units, workflow, proof, frozen, lineage } = loaderData;
  const trail = procedureTrail(record);
  const person = { "@type": "Person", "@id": personId(SITE_ORIGIN), name: SITE.name, url: SITE_ORIGIN };
  const blocks = [breadcrumbJsonLd(SITE_ORIGIN, trail), procedureJsonLd(record, SITE_ORIGIN, person, lineage)];
  return (
    <PageShell
      trail={
        <>
          {blocks.map((block, i) => (
            <script key={i} type="application/ld+json" dangerouslySetInnerHTML={{ __html: serializeJsonLd(block) }} />
          ))}
        </>
      }
    >
      <Breadcrumb trail={trail} />
      <h1 className="page-title">{record.title}</h1>
      {draft ? <p className="procedure-draft">Draft: only you can see this page.</p> : null}
      <ProcedureView record={record} count={count} factor={factor} units={units} workflow={workflow} proof={proof} frozen={frozen} lineage={lineage} />
      {/* Run mode (app/enhance/run.ts): the page is complete as served; this adds the bench checklist. */}
      <Enhance module="run" />
      {/* Keep on this device (app/enhance/offline.ts): a copy that opens with no connection, only when the reader asks. */}
      <Enhance module="offline" />
    </PageShell>
  );
}