Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: Bug report
about: Report something that isn't working as expected
title: ''
labels: bug
labels: "Type: Bug, Status: Pending"
assignees: ''
---

Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: Feature request
about: Suggest a new feature or enhancement
title: ''
labels: enhancement
labels: "Type: Enhancement, Status: Pending"
assignees: ''
---

Expand Down
44 changes: 41 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,40 @@ project setup and local development.
1. [Join our Discord guild](https://discord.gg/tripsit) — development discussion happens there.
2. Follow the **Development - Getting Started** section in [README.md](README.md) to get a local instance running.

## Finding and claiming work

All work is tracked in [GitHub issues](https://github.com/TripSit/website/issues).

- **Find something to do.** Filter by `Status: Available`. `good first issue` marks small, well-scoped ones.
- **Claim it before you start.** Assign yourself and change the status label to `Status: In Progress`. If you stop,
unassign yourself and set it back to `Status: Available`.
- **Mind the area.** Issues with the same `Area:` label usually touch the same files. If one in your area is already
`Status: In Progress`, coordinate with its assignee or wait for it to merge. Issues labelled `dependencies` change
`package.json` or `package-lock.json`, so land them one at a time.
- **Link your work.** Put the issue number in the branch name (`fix/268-oidc-pkce`) and write `Closes #268` in the
pull request, so the issue closes when the PR merges into `uat`.
- **Old review IDs.** Issues moved from the old review tracker keep their ID in the title, for example `(WEB-006)`.
Older branches, commits and PRs refer to them by that ID.

New issues from the templates start as `Status: Pending` with a `Type:` label. Triage gives them a priority and an
area, and moves them to `Status: Available` or `Status: Needs Decision`.

## Labels

Every issue gets one label from each group:

| Group | Labels |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| `Priority:` | `Critical` (P0, fix now) · `High` (P1, real bug or exposure) · `Medium` (P2, should fix) · `Low` (P3, polish) |
| `Type:` | `Bug` · `Enhancement` · `Maintenance` (cleanup, tooling, CI/CD, deploy, dependencies, docs) · `Question` |
| `Status:` | `Pending` (needs triage or confirmation) · `Available` · `Needs Decision` · `Blocked` · `In Progress` · `Review Needed` |
| `Area:` | `Factsheets` · `Homepage` · `Auth` · `UI` · `Forms` · `Tools` · `Content` · `CI` · `Deploy` · `Repo` |

Add any tags that apply: `security`, `performance`, `accessibility`, `mobile`, `seo`, `dependencies`,
`good first issue`, `help wanted`. Each label's description on GitHub says what it covers. Close finished issues
rather than leaving them on `Status: Completed`. Use `duplicate` or `wontfix` when closing an issue that won't be
worked on.

## Branching and pull requests

- `main` is the production branch. `uat` is the staging/integration branch.
Expand All @@ -22,8 +56,8 @@ project setup and local development.

## Running checks before opening a PR

Every pull request into `main` or `uat` runs lint, a production build (which includes a full TypeScript check),
and CodeQL. Run the equivalent checks locally first:
Every pull request into `main` or `uat` runs lint, tests, a production build (which includes a full TypeScript
check), and CodeQL. Run the equivalent checks locally first:

```bash
# Install dependencies (matches CI)
Expand All @@ -32,11 +66,15 @@ npm ci
# Lint
npx eslint .

# Tests
npm test

# Production build (also type-checks)
npx next build
```

Committing runs `lint-staged` automatically via Husky, which lints and fixes your staged files.
Committing runs `lint-staged` automatically via Husky, which lints and fixes your staged files. `npm ci` runs
Husky's `prepare` script, which sets `core.hooksPath`; use `npm ci --ignore-scripts` if you don't want that.

## Scope and safety

Expand Down
598 changes: 0 additions & 598 deletions REVIEW-ISSUES.md

This file was deleted.

4 changes: 2 additions & 2 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import BlogSuggestion from "../components/BlogSuggestion";

const domain = process.env.NEXT_PUBLIC_DNS_DOMAIN || "tripsit.me";

// Never fetch or redirect to URLs built from request headers here (see WEB-002 in
// REVIEW-ISSUES.md); only link to the blog on a fixed host.
// Never fetch or redirect to URLs built from request headers here (SSRF and open redirect,
// WEB-002, fixed in #247); only link to the blog on a fixed host.
export default function NotFound() {
return (
<div className="global">
Expand Down
11 changes: 10 additions & 1 deletion src/components/comboChecker/__tests__/comboData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,23 @@ describe("getCombo", () => {
});
});

it("falls back to the reverse pairing when only that direction is present", () => {
it("returns the same result in either order", () => {
const forward = getCombo("Alcohol", "GHB/GBL");
const reverse = getCombo("GHB/GBL", "Alcohol");
expect(forward).toEqual(reverse);
expect(forward.risk).toBe("dangerous");
expect(forward.note).not.toBe("");
});

it.each([
["DXM", "MXE"],
["MXE", "DXM"],
])("rates %s + %s as Unsafe", (first, second) => {
const result = getCombo(first, second);
expect(result.risk).toBe("unsafe");
expect(result.note).toMatch(/serotonin syndrome/i);
});

it("includes the note when the dataset provides one", () => {
expect(getCombo("LSD", "Cannabis")).toEqual({
risk: "caution",
Expand Down
17 changes: 10 additions & 7 deletions src/components/comboChecker/comboData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Combo Checker data + interaction logic.
// Interaction data is TripSit's canonical combo dataset (comboInteractions.json,
// the same matrix used at combo.tripsit.me). The visual risk system (colors/glyphs)
// comes from the Claude Design "Combo Checker" handoff.
// Interaction data comes from the TripSit drug database.

import rawInteractions from "./comboInteractions.json";
import rawInteractions from "tripsit_drug_db/combos.json";

export type RiskKey =
| "synergy"
Expand All @@ -23,7 +21,7 @@ export interface RiskDef {
}

// Display order + grouping from TripSit's combo chart config (tableOrder / groupNames).
// Substance labels here are lowercased to look up keys in comboInteractions.json.
// Display labels are normalized to database keys when looking up a pair.
export const GROUPS: { name: string; substances: string[] }[] = [
{
name: "Psychedelics",
Expand Down Expand Up @@ -154,12 +152,17 @@ const STATUS_TO_RISK: Record<string, RiskKey> = {
const UNKNOWN_NOTE =
"There isn't enough reliable data on this combination. Treat it as potentially risky and research it thoroughly.";

function comboKey(substance: string): string {
const key = substance.toLowerCase();
return key === "dxm" ? "dextromethorphan" : key;
}

export function getCombo(
a: string,
b: string,
): { risk: RiskKey; note: string } {
const ka = a.toLowerCase();
const kb = b.toLowerCase();
const ka = comboKey(a);
const kb = comboKey(b);
const entry = INTERACTIONS[ka]?.[kb] ?? INTERACTIONS[kb]?.[ka];
if (!entry) return { risk: "unknown", note: UNKNOWN_NOTE };
return {
Expand Down
Loading
Loading