Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
42c0e5b
feat: add react compiler
tefkah Jun 3, 2025
c398df1
feat: add react compiler to babel config for ui
tefkah Jun 3, 2025
bde6318
feat: add eslint config to context-editor
tefkah Jun 3, 2025
b33e4c8
refactor: fix eslint rules in context-editor
tefkah Jun 3, 2025
f9b1fe1
feat: add compiler to storybook
tefkah Jun 3, 2025
17fe001
dev: add demo for react compiler in next
tefkah Jun 3, 2025
9f3313c
dev: actually uprgade next, add react scan, add turbo build option
tefkah Jun 3, 2025
0f49f5f
Merge branch 'main' into tfk/react-compiler-fr
tefkah Jun 3, 2025
4b91bc8
fix: don't compare to string
tefkah Jun 3, 2025
5639aa1
fix: sync up vite versions
tefkah Jun 3, 2025
4b26890
fix: don't have context-editor care about type errors in eslint/config
tefkah Jun 3, 2025
76ae063
fix: include babel-plugin
tefkah Jun 3, 2025
e8bcea1
fix: grrrr tsconfig
tefkah Jun 3, 2025
5364e20
fix: substitute form.watch for useWatch, add compiler to vitest
tefkah Jun 3, 2025
9d9aa96
fix: types
tefkah Jun 3, 2025
e9f22fa
fix: try to maybe fix the weird forgot error
tefkah Jun 3, 2025
d942c8d
ci: handle caching more efficiently for ci
tefkah Jun 3, 2025
09977db
fiX: use proper action name
tefkah Jun 3, 2025
9294c44
fix: force dynamic forgot page to get around annoying error
tefkah Jun 3, 2025
3542921
fix: do a correct check
tefkah Jun 3, 2025
31d7086
chore: remove emit-container-tag-sha job that didnt do anything anymore
tefkah Jun 3, 2025
f9af398
chore: merge
tefkah Aug 12, 2025
8de68c3
feat: upgrade next to 15.4
tefkah Aug 12, 2025
d679867
fix: don't import from storybook react
tefkah Aug 12, 2025
d6be444
chore: merge
tefkah Aug 14, 2025
a486cbd
fix: fix redirct bug, maybe?
tefkah Aug 14, 2025
ec6f269
fix: restore emision
tefkah Aug 14, 2025
dce1aca
fix: remove notfound import
tefkah Aug 14, 2025
fbcab52
chore: revert test compose changes
tefkah Aug 14, 2025
d85c18d
chore: remove console statement
tefkah Aug 14, 2025
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
18 changes: 16 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ jobs:
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT

- name: Setup pnpm cache
# only one task can write to cache at the same time, we make format do that
if: ${{ matrix.task != 'format' }}
uses: actions/cache/restore@v4
with:
path: ${{ steps.get-store-path.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Setup pnpm cache for `format` (and write to it)
# only one task can write to cache at the same time, we make format do that
if: ${{ matrix.task == 'format' }}
uses: actions/cache@v4
with:
path: ${{ steps.get-store-path.outputs.STORE_PATH }}
Expand All @@ -56,13 +68,15 @@ jobs:
${{ runner.os }}-pnpm-store-

# to cache p:build, format, lint, type-check and test-run
# we need to use the task name to avoid conflicts between different tasks
# that use the same cache key
- name: Setup turbo cache
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
key: ${{ runner.os }}-turbo-${{ matrix.task }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
${{ runner.os }}-turbo-${{ matrix.task }}-

- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
Expand Down
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
presets: ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"],
plugins: ["babel-plugin-react-compiler"],
};
2 changes: 1 addition & 1 deletion config/eslint/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
"typeRoots": ["node_modules/@types", "./types.d.ts"]
},
"include": ["."],
"include": ["./**.js", "./**.d.ts"],
"exclude": ["node_modules"]
}
4 changes: 3 additions & 1 deletion core/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ DATACITE_PASSWORD=""
DATACITE_API_URL="https://api.test.datacite.org"

GCLOUD_KEY_FILE='xxx'
VALKEY_HOST='localhost'
VALKEY_HOST='localhost'

REACT_SCAN_ENABLED=true
6 changes: 3 additions & 3 deletions core/actions/http/config/client-components/FieldOutputMap.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useFieldArray } from "react-hook-form";
import { useFieldArray, useWatch } from "react-hook-form";

import type { PubFieldSchemaId, PubFieldsId } from "db/public";
import { AccordionContent, AccordionItem, AccordionTrigger } from "ui/accordion";
Expand Down Expand Up @@ -121,7 +121,7 @@ export const FieldOutputMap = defineCustomFormField(
"outputMap",
function FieldOutputMap({ form, fieldName }) {
const pubFields = Object.values(usePubFieldContext());
const values = form.watch();
const values = useWatch({ control: form.control, name: fieldName });

const { fields, append, remove } = useFieldArray({
control: form.control,
Expand All @@ -131,7 +131,7 @@ export const FieldOutputMap = defineCustomFormField(

const [title] = itemName.split("|");

const alreadySelectedPubFields = values[fieldName] ?? [];
const alreadySelectedPubFields = values ?? [];
const unselectedPubFields = pubFields.filter(
(pubField) =>
!alreadySelectedPubFields.some((field) => field.pubField === pubField.slug)
Expand Down
2 changes: 1 addition & 1 deletion core/app/(user)/forgot/ForgotForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const forgotPasswordSchema = z.object({
email: z.string().email(),
});

export default function ForgotForm() {
export function ForgotForm() {
const form = useForm<z.infer<typeof forgotPasswordSchema>>({
resolver: zodResolver(forgotPasswordSchema),
defaultValues: {
Expand Down
14 changes: 13 additions & 1 deletion core/app/(user)/forgot/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import type { Metadata } from "next";

import React from "react";

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "ui/card";

import ForgotForm from "./ForgotForm";
import { ForgotForm } from "./ForgotForm";

export const dynamic = "force-dynamic";

export const metadata: Metadata = {
title: "Forgot password",
description:
"Enter your account's email address below to receive a secure link for resetting your password.",
};

export default async function Page() {
return (
Expand Down
2 changes: 2 additions & 0 deletions core/app/(user)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export default async function Page() {
communityId: community.id,
redirectUrl: await constructRedirectToBaseCommunityPage({
communitySlug: community.slug,
user,
community,
}),
}))
);
Expand Down
15 changes: 13 additions & 2 deletions core/app/c/[communitySlug]/CommunitySwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { User } from "lucia";

import Link from "next/link";

import { Avatar, AvatarFallback, AvatarImage } from "ui/avatar";
Expand All @@ -17,9 +19,14 @@ import { constructRedirectToBaseCommunityPage } from "~/lib/server/navigation/re
type Props = {
community: NonNullable<CommunityData>;
availableCommunities: NonNullable<CommunityData>[];
user: User;
};

const CommunitySwitcher: React.FC<Props> = async function ({ community, availableCommunities }) {
const CommunitySwitcher: React.FC<Props> = async function ({
community,
availableCommunities,
user,
}) {
const avatarClasses =
"rounded-md w-9 h-9 mr-1 group-data-[collapsible=icon]:h-8 group-data-[collapsible=icon]:w-8 border";
const textClasses = "flex-auto text-base font-semibold w-44 text-left";
Expand All @@ -30,7 +37,11 @@ const CommunitySwitcher: React.FC<Props> = async function ({ community, availabl
const communityRedirectUrls = await Promise.all(
availableCommunities.map(async (option) => ({
communityId: option.id,
redirectUrl: await constructRedirectToBaseCommunityPage({ communitySlug: option.slug }),
redirectUrl: await constructRedirectToBaseCommunityPage({
communitySlug: option.slug,
community,
user,
}),
}))
);

Expand Down
1 change: 1 addition & 0 deletions core/app/c/[communitySlug]/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ const SideNav: React.FC<Props> = async function ({ community, availableCommuniti
<SidebarMenu>
<SidebarMenuItem className={`h-full`}>
<CommunitySwitcher
user={user}
community={community}
availableCommunities={availableCommunities}
/>
Expand Down
9 changes: 4 additions & 5 deletions core/app/c/[communitySlug]/fields/FieldForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { UseFormReturn } from "react-hook-form";

import { useCallback, useEffect } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useForm, useWatch } from "react-hook-form";
import { SCHEMA_TYPES_WITH_ICONS } from "schemas";
import { z } from "zod";

Expand Down Expand Up @@ -71,7 +71,7 @@ const DEFAULT_VALUES = {
isRelation: false,
} as unknown as DefaultFieldFormValues;

type FormType = UseFormReturn<FormValues, any, undefined>;
type FormType = UseFormReturn<FormValues, any, FormValues>;

const SchemaSelectField = ({ form, isDisabled }: { form: FormType; isDisabled?: boolean }) => {
const schemaTypes = Object.values(CoreSchemaType).filter((v) => v !== CoreSchemaType.Null);
Expand Down Expand Up @@ -146,9 +146,8 @@ const SlugField = ({
communitySlug: string;
readOnly?: boolean;
}) => {
const { watch, setValue } = form;

const watchName = watch("name");
const { setValue } = form;
const watchName = useWatch({ control: form.control, name: "name" });

useEffect(() => {
if (!readOnly) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useForm, useWatch } from "react-hook-form";
import { z } from "zod";

import type { CreateTokenFormContext as CreateTokenFormContextType } from "db/types";
Expand Down Expand Up @@ -81,7 +81,7 @@ export const CreateTokenForm = () => {
}
};
// this `as const` should not be necessary, not sure why it is
const token = form.watch("token" as const);
const token = useWatch({ control: form.control, name: "token" });

return (
<Form {...form}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ControllerRenderProps, FieldValue, UseFormReturn } from "react-hoo

import { useCallback, useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useForm, useWatch } from "react-hook-form";
import { z } from "zod";

import type {
Expand Down Expand Up @@ -199,9 +199,12 @@ export const StagePanelRuleCreator = (props: Props) => {
},
});

const event = form.watch("event");
const selectedActionInstanceId = form.watch("actionInstanceId");
const sourceActionInstanceId = form.watch("sourceActionInstanceId");
const event = useWatch({ control: form.control, name: "event" });
const selectedActionInstanceId = useWatch({ control: form.control, name: "actionInstanceId" });
const sourceActionInstanceId = useWatch({
control: form.control,
name: "sourceActionInstanceId",
});

// for action chaining events, filter out self-references
const isActionChainingEvent = event === Event.actionSucceeded || event === Event.actionFailed;
Expand Down
95 changes: 95 additions & 0 deletions core/app/compiler-demo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client";

import React, { useState } from "react";

function SlowComponent(props: { unused?: any }) {
const largeArray = Array.from({ length: 10000 }, (_, i) => i);

return (
<div className="flex flex-wrap gap-1 overflow-scroll">
{largeArray.map((value) => (
<div
key={value}
className="h-2 w-2 bg-neutral-700"
style={{
backgroundColor: `rgb(${value % 255}, ${(value * 2) % 255}, ${(value * 3) % 255})`,
}}
></div>
))}
</div>
);
}

function CounterButton(props: { onClick: () => void }) {
return (
<button
onClick={props.onClick}
className="rounded border border-white/20 bg-neutral-700 px-4 py-2 text-white hover:bg-neutral-600"
>
Increase count
</button>
);
}

function ColorPicker(props: { value: string; onChange: (value: string) => void }) {
return (
<input
type="color"
value={props.value}
onChange={(e) => props.onChange(e.target.value)}
className="h-12 w-full cursor-pointer rounded border border-white/20 bg-neutral-700 p-1"
/>
);
}

export function DemoComponent() {
const [count, setCount] = useState(0);
const [color, setColor] = useState("#ffffff");

return (
<div className={`flex gap-8`}>
<div className="flex h-64 w-96 flex-col gap-4 border border-white p-4">
<h2 className="mb-8 text-center text-xl font-bold">Color Picker</h2>
<ColorPicker value={color} onChange={(e) => setColor(e)} />
<div className="mt-2">
Current value: <br />
<span className="font-mono">{color}</span>
</div>
</div>
<div className="flex h-64 w-96 flex-col gap-4 border border-white p-4">
<h2 className="mb-8 text-center text-xl font-bold">Counter</h2>
<CounterButton onClick={() => setCount((count) => count + 1)} />
<div className="mt-2">
Current value: <br />
<span className="font-mono">{count}</span>
</div>
</div>
<div className="flex h-64 w-96 flex-col gap-2 border border-white p-4">
<h2 className="text-center text-xl font-bold">A Slow Component</h2>
<span className="text-center font-light text-neutral-200">
(This component renders 10,000 boxes)
</span>
<SlowComponent unused={{ name: "nope" }} />
</div>
</div>
);
}
export default function Demo() {
const [color, setColor] = useState("#ffffff");
return (
<div className="flex min-h-screen flex-col">
<h1 className="py-8 text-center text-2xl font-bold">React Compiler Demo</h1>
<p className="text-center text-sm text-neutral-400">
Turn off the compiler in <code>next.config.ts</code> to see the difference.
</p>
<p className="text-center text-sm text-neutral-400">
Picking a color should be really slow with the compiler turned off
</p>

<div className={`flex flex-grow items-center justify-center`}>
<DemoComponent />
<ColorPicker value={color} onChange={(e) => setColor(e)} />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMemo } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, useFormContext } from "react-hook-form";
import { useForm, useFormContext, useWatch } from "react-hook-form";
import { z } from "zod";

import type { StagesId } from "db/public";
Expand Down Expand Up @@ -117,7 +117,8 @@ export const ButtonConfigurationForm = ({
});
dispatch({ eventName: "save" });
};
const labelValue = form.watch("label");

const labelValue = useWatch({ control: form.control, name: "label" });

return (
<Form {...form}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ControllerRenderProps } from "react-hook-form";

import { useEffect, useState } from "react";
import { Pencil, PlusIcon, TrashIcon } from "lucide-react";
import { useFormContext } from "react-hook-form";
import { useFormContext, useWatch } from "react-hook-form";

import type { InputComponent } from "db/public";
import { Button } from "ui/button";
Expand Down Expand Up @@ -128,8 +128,8 @@ export const FormBuilderColorPickerPopover = ({
export default (props: ComponentConfigFormProps<InputComponent.colorPicker>) => {
// for some reason if i use `props.form` the watched values don't update when the form values change
const reactiveForm = useFormContext<ConfigFormData<InputComponent.colorPicker>>();
const presets = reactiveForm.watch("config.presets");
const presetsOnly = reactiveForm.watch("config.presetsOnly");
const presets = useWatch({ control: reactiveForm.control, name: "config.presets" });
const presetsOnly = useWatch({ control: reactiveForm.control, name: "config.presetsOnly" });

const presetsOnlyEnabled = Boolean(presets?.length);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import dynamic from "next/dynamic";
import { typeboxResolver } from "@hookform/resolvers/typebox";
import { Type } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { useForm } from "react-hook-form";
import { useForm, useWatch } from "react-hook-form";
import { componentConfigSchemas, componentsBySchema, relationBlockConfigSchema } from "schemas";

import type { PubsId, PubTypesId } from "db/public";
Expand Down Expand Up @@ -348,7 +348,7 @@ export const InputComponentConfigurationForm = ({ index, fieldInputElement }: Pr

useUnsavedChangesWarning(form.formState.isDirty);

const component = form.watch("component");
const component = useWatch({ control: form.control, name: "component" });

const onSubmit = (values: ConfigFormData<typeof component>) => {
const schema = isRelation
Expand Down
Loading
Loading