Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/PrairieLearn/PrairieLearn --skill trpc명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
| name | trpc |
| description | Conventions for writing tRPC routers and procedures. |
trpc.ts.trpc/assessment/), adjust the ResLocalsForPage type, context fields, URL helper in lib/client/url.ts, and mount path in server.ts.pages/instructorInstanceAdminSettings/). Key pieces: generatePrefixCsrfToken from @prairielearn/signed-token server-side, the scope's client.ts and context.ts, and QueryClientProviderDebug from @prairielearn/trpc/react client-side.tRPC routers are mounted per authorization scope, not per page. Each scope has its own directory under apps/prairielearn/src/trpc/ and is mounted behind the corresponding Express authorization middleware chain.
| Scope | Directory | Mount path | ResLocalsForPage type |
|---|---|---|---|
| administrator | trpc/administrator/ | /pl/administrator/trpc | 'plain' |
| course | (create when needed) | /pl/course/:course_id/trpc | 'course' |
| course instance | trpc/courseInstance/ | /pl/course_instance/:id/instructor/trpc | 'course-instance' |
| assessment | trpc/assessment/ | .../assessment/:assessment_id/trpc | 'assessment' |
| assessment question | trpc/assessmentQuestion/ | .../assessment/:aid/assessment_question/:aqid/trpc | 'instructor-assessment-question' |
| assessment instance | (create when needed) | .../assessment_instance/:ai_id/trpc | 'assessment-instance' |
| instance question | (create when needed) | .../instance_question/:iq_id/trpc | 'instance-question' |
Do not create per-page tRPC routers.
Every scope directory contains: init.ts, trpc.ts, client.ts, context.ts, plus one *.ts file per subrouter and optional *.sql files for scope-specific queries. All routers use superjson as the transformer in both init.ts and client.ts.
See any existing scope (e.g. trpc/assessment/) for the exact boilerplate. The files follow a mechanical pattern — init.ts creates the tRPC instance and authorization middleware, trpc.ts composes subrouters and exports Express middleware via createExpressMiddleware, client.ts creates the HTTP client with CSRF headers, context.ts exports TRPCProvider/useTRPC via createTRPCContext.
Shared infrastructure comes from explicit @prairielearn/trpc subpaths:
@prairielearn/trpc/server for appErrorFormatter and throwAppError.@prairielearn/trpc/client for AppError, getAppError, and renderAppError.@prairielearn/trpc/react for AppErrorAlert and QueryClientProviderDebug.@prairielearn/trpc/express for tRPC request detection, multipart detection, and pre-adapter error responses.Do not create a global tRPC initialization factory. Each scope initializes its context-bound t in its own init.ts with the shared appErrorFormatter.
list, upsert, destroy — not listWidgets or deleteMutation.Mutation/Procedure suffix (e.g. const setModeMutation = t.procedure...), but the router key must not: setMode: setModeMutation.{feature}Router (e.g. widgetsRouter). Router keys: camelCase (widgets).requireCourseInstancePermissionView, etc.) is defined in init.ts and chained with .use().features.enabled(...)) goes in the subrouter file, not init.ts. See trpc/assessment/access-control.ts for an example.createContext includes the full locals object alongside extracted fields as an escape hatch.lib/client/safe-db-types.ts (e.g. StaffStudentLabelSchema). Always .parse() records through the schema before returning.models/ instead of one-off SQL.trpc.subrouter.procedure.queryOptions() with useQuery() for queries. Do not manually construct { queryKey, queryFn } objects — the generated queryOptions() provides type-safe keys and avoids stale cache issues.trpc.subrouter.procedure.mutationOptions() with useMutation() for mutations.trpc proxy via the scope's useTRPC() hook from context.ts.The CSRF token is generated server-side with generatePrefixCsrfToken using the scope's URL helper from lib/client/url.ts, passed as a prop to the hydrated component, and sent by the tRPC client as an X-CSRF-Token header. The Express CSRF middleware validates it before the request reaches the tRPC router.
TRPCError. Import throwAppError from @prairielearn/trpc/server and use it only when the client needs extra fields beyond message (e.g. jobSequenceId to link to logs) or must branch on the code. A code that the client just renders as message is indistinguishable from UNKNOWN — don't add it.never for procedures that have no typed errors:
export interface AssessmentGroupsError {
AddGroup: never;
EnableGroupWork: { code: 'SYNC_JOB_FAILED'; jobSequenceId: string };
}
Don't use Record<string, never> or empty interface XError {}.message: string is added automatically by throwAppError and getAppError — only declare procedure-specific fields.getAppError<XError['Procedure']>(mutation.error) from @prairielearn/trpc/client — procedure-keyed even when the entry is never. Never read raw mutation/query errors.<AppErrorAlert> from @prairielearn/trpc/react or renderAppError from @prairielearn/trpc/client, not {appError.message} directly. The exhaustive renderer map makes it a compile error to forget a variant or silently drop fields like jobSequenceId:
<AppErrorAlert
error={copyError}
onDismiss={() => copyMutation.reset()}
render={{
SYNC_JOB_FAILED: ({ message, jobSequenceId }) => (
),
: message,
}}
/>
Use when you need a to pass elsewhere (e.g. 's ).tRPC procedures are tested as integration tests via HTTP. See tests/instructorStudentsLabels.test.ts for the pattern: create a client with generatePrefixCsrfToken + the scope's create*TrpcClient, call procedures directly, assert with TRPCClientError for error cases.
renderAppError(error, {...})ReactNodeStickySaveBaralert.messagepages/instructorStudentsLabels/components/LabelModifyModal.tsx (typed) and pages/administratorCourses/administratorCourses.html.tsx (never-keyed) for examples.