···11{
22 "db_name": "PostgreSQL",
33- "query": "\n SELECT DISTINCT json->>'nsid' as collection_nsid\n FROM record\n WHERE collection = 'network.slices.lexicon'\n AND json->>'slice' = $1\n AND json->>'nsid' IS NOT NULL\n AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'\n ORDER BY json->>'nsid'\n ",
33+ "query": "\n SELECT DISTINCT json->>'nsid' as collection_nsid\n FROM record\n WHERE collection = 'network.slices.lexicon'\n AND json->>'slice' = $1\n AND json->>'nsid' IS NOT NULL\n AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'\n AND (json->>'excludedFromSync' IS NULL OR json->>'excludedFromSync' != 'true')\n ORDER BY json->>'nsid'\n ",
44 "describe": {
55 "columns": [
66 {
···1818 null
1919 ]
2020 },
2121- "hash": "112e2cbb7ee10d0ec1261e8beebda6c126bf23dea5a8f3fe9f7e9952807a478f"
2121+ "hash": "a371b03a7a67b4e2683bf191d44b5eaae5d514096b5cdf48806083618d59ac64"
2222}
···11{
22 "db_name": "PostgreSQL",
33- "query": "\n WITH slice_collections AS (\n SELECT DISTINCT\n json->>'nsid' as collection_nsid\n FROM record\n WHERE collection = 'network.slices.lexicon'\n AND json->>'slice' = $1\n AND json->>'nsid' IS NOT NULL\n AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'\n )\n SELECT COUNT(*) as count\n FROM record r\n INNER JOIN slice_collections sc ON r.collection = sc.collection_nsid\n WHERE r.slice_uri = $1\n ",
33+ "query": "\n WITH slice_collections AS (\n SELECT DISTINCT\n json->>'nsid' as collection_nsid\n FROM record\n WHERE collection = 'network.slices.lexicon'\n AND json->>'slice' = $1\n AND json->>'nsid' IS NOT NULL\n AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'\n AND (json->>'excludedFromSync' IS NULL OR json->>'excludedFromSync' != 'true')\n )\n SELECT COUNT(*) as count\n FROM record r\n INNER JOIN slice_collections sc ON r.collection = sc.collection_nsid\n WHERE r.slice_uri = $1\n ",
44 "describe": {
55 "columns": [
66 {
···1818 null
1919 ]
2020 },
2121- "hash": "e62f397eb38f35e17988429c36de83f0e299c7def29dd1452706563ee58f9e3f"
2121+ "hash": "1bba58bc784972353c054d767a7337f0d472d8d3d9484ebb18237f4a094c4e58"
2222}
+3
api/src/database.rs
···537537 AND json->>'slice' = $1
538538 AND json->>'nsid' IS NOT NULL
539539 AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'
540540+ AND (json->>'excludedFromSync' IS NULL OR json->>'excludedFromSync' != 'true')
540541 )
541542 SELECT
542543 r.collection,
···575576 AND json->>'slice' = $1
576577 AND json->>'nsid' IS NOT NULL
577578 AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'
579579+ AND (json->>'excludedFromSync' IS NULL OR json->>'excludedFromSync' != 'true')
578580 ORDER BY json->>'nsid'
579581 "#,
580582 slice_uri
···599601 AND json->>'slice' = $1
600602 AND json->>'nsid' IS NOT NULL
601603 AND (json->>'definitions')::jsonb->'main'->>'type' = 'record'
604604+ AND (json->>'excludedFromSync' IS NULL OR json->>'excludedFromSync' != 'true')
602605 )
603606 SELECT COUNT(*) as count
604607 FROM record r
+3-1
frontend/src/client.ts
···11// Generated TypeScript client for AT Protocol records
22-// Generated at: 2025-09-19 15:56:11 UTC
22+// Generated at: 2025-09-19 22:48:29 UTC
33// Lexicons: 25
4455/**
···12011201 updatedAt?: string;
12021202 /** AT-URI reference to the slice this lexicon belongs to */
12031203 slice: string;
12041204+ /** Whether this lexicon should be excluded from sync operations */
12051205+ excludedFromSync?: boolean;
12041206}
1205120712061208export type NetworkSlicesLexiconSortFields =
+80-1
frontend/src/features/slices/lexicon/handlers.tsx
···22import { renderHTML } from "../../../utils/render.tsx";
33import { requireAuth, withAuth } from "../../../routes/middleware.ts";
44import { getSliceClient } from "../../../utils/client.ts";
55-import { buildSliceUri } from "../../../utils/at-uri.ts";
55+import { buildSliceUri, getRkeyFromUri } from "../../../utils/at-uri.ts";
66import { withSliceAccess } from "../../../routes/slice-middleware.ts";
77import { extractSliceParams } from "../../../utils/slice-params.ts";
88import { SliceLexiconPage } from "./templates/SliceLexiconPage.tsx";
···1111import { LexiconErrorMessage } from "./templates/fragments/LexiconErrorMessage.tsx";
1212import { LexiconsList } from "./templates/fragments/LexiconsList.tsx";
1313import { LexiconFormModal } from "./templates/fragments/LexiconFormModal.tsx";
1414+import { Badge } from "../../../shared/fragments/Badge.tsx";
1415import { FileCode } from "lucide-preact";
1516import { buildSliceUrl } from "../../../utils/slice-params.ts";
1617import type { NetworkSlicesLexicon } from "../../../client.ts";
···252253 definitions: lexicon.value.definitions,
253254 uri: lexicon.uri,
254255 createdAt: lexicon.value.createdAt,
256256+ excludedFromSync: lexicon.value.excludedFromSync === true,
255257 currentUser: authContext.currentUser,
256258 hasSliceAccess: context.sliceContext?.hasAccess,
257259 })
···511513 return renderHTML(<LexiconFormModal sliceId={sliceId} />);
512514}
513515516516+async function handleUpdateLexiconExclusion(
517517+ req: Request,
518518+ params?: URLPatternResult
519519+): Promise<Response> {
520520+ const context = await withAuth(req);
521521+ const authResponse = requireAuth(context);
522522+ if (authResponse) return authResponse;
523523+524524+ const sliceId = params?.pathname.groups.id;
525525+ if (!sliceId) {
526526+ return new Response("Invalid slice ID", { status: 400 });
527527+ }
528528+529529+ // Get URI from query parameters
530530+ const url = new URL(req.url);
531531+ const uri = url.searchParams.get("uri");
532532+ if (!uri) {
533533+ return new Response("URI parameter is required", { status: 400 });
534534+ }
535535+536536+ try {
537537+ const formData = await req.formData();
538538+ const nsid = formData.get("nsid") as string;
539539+ const excludedFromSync = formData.get("excludedFromSync") === "true";
540540+541541+ if (!nsid) {
542542+ return new Response("NSID is required", { status: 400 });
543543+ }
544544+545545+ const sliceClient = getSliceClient(context, sliceId);
546546+547547+ // Get the current lexicon record using the URI
548548+ const currentRecord = await sliceClient.network.slices.lexicon.getRecord({
549549+ uri
550550+ });
551551+552552+ // Update the record with the new exclusion status
553553+ const updatedRecord = {
554554+ ...currentRecord.value,
555555+ excludedFromSync,
556556+ updatedAt: new Date().toISOString(),
557557+ };
558558+559559+ // Extract rkey from URI for updateRecord
560560+ const rkey = getRkeyFromUri(uri);
561561+ await sliceClient.network.slices.lexicon.updateRecord(rkey, updatedRecord);
562562+563563+ // Return the updated status badge
564564+ return renderHTML(
565565+ <div id="exclusion-status">
566566+ {excludedFromSync ? (
567567+ <Badge variant="warning" className="min-w-[100px] justify-center">
568568+ Sync excluded
569569+ </Badge>
570570+ ) : (
571571+ <Badge variant="success" className="min-w-[100px] justify-center">
572572+ Sync enabled
573573+ </Badge>
574574+ )}
575575+ </div>
576576+ );
577577+ } catch (error) {
578578+ console.error("Failed to update lexicon exclusion:", error);
579579+ return renderHTML(
580580+ <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
581581+ <p>Failed to update exclusion status: {error}</p>
582582+ </div>,
583583+ { status: 500 }
584584+ );
585585+ }
586586+}
587587+514588export const lexiconRoutes: Route[] = [
515589 {
516590 method: "GET",
···557631 method: "DELETE",
558632 pattern: new URLPattern({ pathname: "/api/slices/:id/lexicons/:rkey" }),
559633 handler: handleDeleteLexicon,
634634+ },
635635+ {
636636+ method: "PUT",
637637+ pattern: new URLPattern({ pathname: "/api/slices/:id/lexicons/exclusion" }),
638638+ handler: handleUpdateLexiconExclusion,
560639 },
561640];