B: Improve typechecks and fix backend API data

This commit is contained in:
Ivan 2025-03-02 19:08:28 +03:00
parent 54472ef0b1
commit 32f83c4122
19 changed files with 90 additions and 68 deletions

View File

@ -203,6 +203,7 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
def to_representation(self, instance: LibraryItem): def to_representation(self, instance: LibraryItem):
result = LibraryItemDetailsSerializer(instance).data result = LibraryItemDetailsSerializer(instance).data
del result['versions']
oss = OperationSchema(instance) oss = OperationSchema(instance)
result['items'] = [] result['items'] = []
for operation in oss.operations().order_by('pk'): for operation in oss.operations().order_by('pk'):

View File

@ -13,10 +13,10 @@ from .basics import (
) )
from .data_access import ( from .data_access import (
CstCreateSerializer, CstCreateSerializer,
CstInfoSerializer,
CstListSerializer, CstListSerializer,
CstMoveSerializer, CstMoveSerializer,
CstRenameSerializer, CstRenameSerializer,
CstSerializer,
CstSubstituteSerializer, CstSubstituteSerializer,
CstTargetSerializer, CstTargetSerializer,
CstUpdateSerializer, CstUpdateSerializer,

View File

@ -30,13 +30,12 @@ class CstBaseSerializer(serializers.ModelSerializer):
read_only_fields = ('id',) read_only_fields = ('id',)
class CstSerializer(serializers.ModelSerializer): class CstInfoSerializer(serializers.ModelSerializer):
''' Serializer: Constituenta data. ''' ''' Serializer: Constituenta public information. '''
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = Constituenta model = Constituenta
exclude = ('order',) exclude = ('order', 'schema')
read_only_fields = ('id', 'schema', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
class CstUpdateSerializer(serializers.Serializer): class CstUpdateSerializer(serializers.Serializer):
@ -100,7 +99,7 @@ class RSFormSerializer(serializers.ModelSerializer):
child=serializers.IntegerField() child=serializers.IntegerField()
) )
items = serializers.ListField( items = serializers.ListField(
child=CstSerializer() child=CstInfoSerializer()
) )
inheritance = serializers.ListField( inheritance = serializers.ListField(
child=InheritanceDataSerializer() child=InheritanceDataSerializer()
@ -136,8 +135,7 @@ class RSFormSerializer(serializers.ModelSerializer):
result['oss'] = [] result['oss'] = []
result['inheritance'] = [] result['inheritance'] = []
for cst in RSForm(instance).constituents().defer('order').order_by('order'): for cst in RSForm(instance).constituents().defer('order').order_by('order'):
result['items'].append(CstSerializer(cst).data) result['items'].append(CstInfoSerializer(cst).data)
del result['items'][-1]['schema']
for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'): for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'):
result['oss'].append({ result['oss'].append({
'id': oss.pk, 'id': oss.pk,

View File

@ -92,7 +92,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
return Response( return Response(
status=c.HTTP_201_CREATED, status=c.HTTP_201_CREATED,
data={ data={
'new_cst': s.CstSerializer(new_cst).data, 'new_cst': s.CstInfoSerializer(new_cst).data,
'schema': s.RSFormParseSerializer(schema.model).data 'schema': s.RSFormParseSerializer(schema.model).data
} }
) )
@ -102,7 +102,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
tags=['RSForm'], tags=['RSForm'],
request=s.CstUpdateSerializer, request=s.CstUpdateSerializer,
responses={ responses={
c.HTTP_200_OK: s.CstSerializer, c.HTTP_200_OK: s.CstInfoSerializer,
c.HTTP_400_BAD_REQUEST: None, c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None, c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None c.HTTP_404_NOT_FOUND: None
@ -122,7 +122,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
PropagationFacade.after_update_cst(schema, cst, data, old_data) PropagationFacade.after_update_cst(schema, cst, data, old_data)
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.CstSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data data=s.CstInfoSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data
) )
@extend_schema( @extend_schema(
@ -202,7 +202,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data={ data={
'new_cst': s.CstSerializer(cst).data, 'new_cst': s.CstInfoSerializer(cst).data,
'schema': s.RSFormParseSerializer(schema.model).data 'schema': s.RSFormParseSerializer(schema.model).data
} }
) )

View File

@ -117,6 +117,20 @@ class UserSerializer(serializers.ModelSerializer):
return attrs return attrs
class UserInfoSerializer(serializers.ModelSerializer):
''' Serializer: User open information. '''
id = serializers.IntegerField(read_only=True)
class Meta:
''' serializer metadata. '''
model = models.User
fields = [
'id',
'first_name',
'last_name',
]
class ChangePasswordSerializer(serializers.Serializer): class ChangePasswordSerializer(serializers.Serializer):
''' Serializer: Change password. ''' ''' Serializer: Change password. '''
old_password = serializers.CharField(required=True) old_password = serializers.CharField(required=True)

View File

@ -73,7 +73,7 @@ class AuthAPIView(generics.RetrieveAPIView):
class ActiveUsersView(generics.ListAPIView): class ActiveUsersView(generics.ListAPIView):
''' Endpoint: Get list of active users. ''' ''' Endpoint: Get list of active users. '''
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = s.UserSerializer serializer_class = s.UserInfoSerializer
def get_queryset(self): def get_queryset(self):
return m.User.objects.filter(is_active=True) return m.User.objects.filter(is_active=True)

View File

@ -20,7 +20,8 @@ export default [
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
globals: { ...globals.browser, ...globals.es2020, ...globals.jest }, globals: { ...globals.browser, ...globals.es2020, ...globals.jest },
project: ['./tsconfig.json', './tsconfig.node.json'] project: ['./tsconfig.json', './tsconfig.node.json'],
projectService: true
} }
} }
}, },

View File

@ -15,7 +15,7 @@ export interface ICurrentUser {
/** /**
* Represents login data, used to authenticate users. * Represents login data, used to authenticate users.
*/ */
export const schemaUserLogin = z.object({ export const schemaUserLogin = z.strictObject({
username: z.string().nonempty(errorMsg.requiredField), username: z.string().nonempty(errorMsg.requiredField),
password: z.string().nonempty(errorMsg.requiredField) password: z.string().nonempty(errorMsg.requiredField)
}); });

View File

@ -51,7 +51,7 @@ export type IVersionUpdateDTO = z.infer<typeof schemaVersionUpdate>;
// ======= SCHEMAS ========= // ======= SCHEMAS =========
export const schemaLibraryItem = z.object({ export const schemaLibraryItem = z.strictObject({
id: z.coerce.number(), id: z.coerce.number(),
item_type: z.nativeEnum(LibraryItemType), item_type: z.nativeEnum(LibraryItemType),
title: z.string(), title: z.string(),
@ -112,7 +112,7 @@ export const schemaCreateLibraryItem = z
message: errorMsg.requiredField message: errorMsg.requiredField
}); });
export const schemaUpdateLibraryItem = z.object({ export const schemaUpdateLibraryItem = z.strictObject({
id: z.number(), id: z.number(),
item_type: z.nativeEnum(LibraryItemType), item_type: z.nativeEnum(LibraryItemType),
title: z.string().nonempty(errorMsg.requiredField), title: z.string().nonempty(errorMsg.requiredField),
@ -122,20 +122,20 @@ export const schemaUpdateLibraryItem = z.object({
read_only: z.boolean() read_only: z.boolean()
}); });
export const schemaVersionInfo = z.object({ export const schemaVersionInfo = z.strictObject({
id: z.coerce.number(), id: z.coerce.number(),
version: z.string(), version: z.string(),
description: z.string(), description: z.string(),
time_create: z.string().datetime({ offset: true }) time_create: z.string().datetime({ offset: true })
}); });
export const schemaVersionUpdate = z.object({ export const schemaVersionUpdate = z.strictObject({
id: z.number(), id: z.number(),
version: z.string().nonempty(errorMsg.requiredField), version: z.string().nonempty(errorMsg.requiredField),
description: z.string() description: z.string()
}); });
export const schemaVersionCreate = z.object({ export const schemaVersionCreate = z.strictObject({
version: z.string(), version: z.string(),
description: z.string(), description: z.string(),
items: z.array(z.number()) items: z.array(z.number())

View File

@ -18,7 +18,7 @@ import { SelectLocationHead } from '../components/SelectLocationHead';
import { LocationHead } from '../models/library'; import { LocationHead } from '../models/library';
import { combineLocation, validateLocation } from '../models/libraryAPI'; import { combineLocation, validateLocation } from '../models/libraryAPI';
const schemaLocation = z.object({ const schemaLocation = z.strictObject({
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation }) location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation })
}); });

View File

@ -58,7 +58,7 @@ export type IConstituentaReference = z.infer<typeof schemaConstituentaReference>
// ====== Schemas ====== // ====== Schemas ======
export const schemaOperation = z.object({ export const schemaOperation = z.strictObject({
id: z.number(), id: z.number(),
operation_type: z.nativeEnum(OperationType), operation_type: z.nativeEnum(OperationType),
oss: z.number(), oss: z.number(),
@ -93,15 +93,15 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
substitutions: z.array(schemaCstSubstituteInfo) substitutions: z.array(schemaCstSubstituteInfo)
}); });
export const schemaOperationPosition = z.object({ export const schemaOperationPosition = z.strictObject({
id: z.number(), id: z.number(),
position_x: z.number(), position_x: z.number(),
position_y: z.number() position_y: z.number()
}); });
export const schemaOperationCreate = z.object({ export const schemaOperationCreate = z.strictObject({
positions: z.array(schemaOperationPosition), positions: z.array(schemaOperationPosition),
item_data: z.object({ item_data: z.strictObject({
alias: z.string().nonempty(), alias: z.string().nonempty(),
operation_type: z.nativeEnum(OperationType), operation_type: z.nativeEnum(OperationType),
title: z.string(), title: z.string(),
@ -114,33 +114,33 @@ export const schemaOperationCreate = z.object({
create_schema: z.boolean() create_schema: z.boolean()
}); });
export const schemaOperationCreatedResponse = z.object({ export const schemaOperationCreatedResponse = z.strictObject({
new_operation: schemaOperation, new_operation: schemaOperation,
oss: schemaOperationSchema oss: schemaOperationSchema
}); });
export const schemaOperationDelete = z.object({ export const schemaOperationDelete = z.strictObject({
target: z.number(), target: z.number(),
positions: z.array(schemaOperationPosition), positions: z.array(schemaOperationPosition),
keep_constituents: z.boolean(), keep_constituents: z.boolean(),
delete_schema: z.boolean() delete_schema: z.boolean()
}); });
export const schemaInputUpdate = z.object({ export const schemaInputUpdate = z.strictObject({
target: z.number(), target: z.number(),
positions: z.array(schemaOperationPosition), positions: z.array(schemaOperationPosition),
input: z.number().nullable() input: z.number().nullable()
}); });
export const schemaInputCreatedResponse = z.object({ export const schemaInputCreatedResponse = z.strictObject({
new_schema: schemaLibraryItem, new_schema: schemaLibraryItem,
oss: schemaOperationSchema oss: schemaOperationSchema
}); });
export const schemaOperationUpdate = z.object({ export const schemaOperationUpdate = z.strictObject({
target: z.number(), target: z.number(),
positions: z.array(schemaOperationPosition), positions: z.array(schemaOperationPosition),
item_data: z.object({ item_data: z.strictObject({
alias: z.string().nonempty(errorMsg.requiredField), alias: z.string().nonempty(errorMsg.requiredField),
title: z.string(), title: z.string(),
comment: z.string() comment: z.string()
@ -149,12 +149,12 @@ export const schemaOperationUpdate = z.object({
substitutions: z.array(schemaCstSubstitute) substitutions: z.array(schemaCstSubstitute)
}); });
export const schemaCstRelocate = z.object({ export const schemaCstRelocate = z.strictObject({
destination: z.number().nullable(), destination: z.number().nullable(),
items: z.array(z.number()).refine(data => data.length > 0) items: z.array(z.number()).refine(data => data.length > 0)
}); });
export const schemaConstituentaReference = z.object({ export const schemaConstituentaReference = z.strictObject({
id: z.number(), id: z.number(),
schema: z.number() schema: z.number()
}); });

View File

@ -19,7 +19,7 @@ import { OperationTooltip } from '../../components/OperationTooltip';
import { OssEditState, OssTabID } from './OssEditContext'; import { OssEditState, OssTabID } from './OssEditContext';
import { OssTabs } from './OssTabs'; import { OssTabs } from './OssTabs';
const paramsSchema = z.object({ const paramsSchema = z.strictObject({
id: z.coerce.number(), id: z.coerce.number(),
tab: z.preprocess(v => (v ? Number(v) : undefined), z.nativeEnum(OssTabID).default(OssTabID.GRAPH)) tab: z.preprocess(v => (v ? Number(v) : undefined), z.nativeEnum(OssTabID).default(OssTabID.GRAPH))
}); });

View File

@ -11,15 +11,15 @@ export type ILexemeResponse = z.infer<typeof schemaLexemeResponse>;
// ====== Schemas ========= // ====== Schemas =========
export const schemaTextResult = z.object({ export const schemaTextResult = z.strictObject({
result: z.string() result: z.string()
}); });
export const schemaWordForm = z.object({ export const schemaWordForm = z.strictObject({
text: z.string(), text: z.string(),
grams: z.string() grams: z.string()
}); });
export const schemaLexemeResponse = z.object({ export const schemaLexemeResponse = z.strictObject({
items: z.array(schemaWordForm) items: z.array(schemaWordForm)
}); });

View File

@ -266,7 +266,7 @@ export enum RSErrorType {
} }
// ========= SCHEMAS ======== // ========= SCHEMAS ========
export const schemaConstituentaBasics = z.object({ export const schemaConstituentaBasics = z.strictObject({
id: z.coerce.number(), id: z.coerce.number(),
alias: z.string().nonempty(errorMsg.requiredField), alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(), convention: z.string(),
@ -276,16 +276,16 @@ export const schemaConstituentaBasics = z.object({
definition_resolved: z.string(), definition_resolved: z.string(),
term_raw: z.string(), term_raw: z.string(),
term_resolved: z.string(), term_resolved: z.string(),
term_forms: z.array(z.object({ text: z.string(), tags: z.string() })) term_forms: z.array(z.strictObject({ text: z.string(), tags: z.string() }))
}); });
export const schemaConstituenta = schemaConstituentaBasics.extend({ export const schemaConstituenta = schemaConstituentaBasics.extend({
parse: z.object({ parse: z.strictObject({
status: z.nativeEnum(ParsingStatus), status: z.nativeEnum(ParsingStatus),
valueClass: z.nativeEnum(ValueClass), valueClass: z.nativeEnum(ValueClass),
typification: z.string(), typification: z.string(),
syntaxTree: z.string(), syntaxTree: z.string(),
args: z.array(z.object({ alias: z.string(), typification: z.string() })) args: z.array(z.strictObject({ alias: z.string(), typification: z.string() }))
}) })
}); });
@ -297,17 +297,17 @@ export const schemaRSForm = schemaLibraryItem.extend({
items: z.array(schemaConstituenta), items: z.array(schemaConstituenta),
inheritance: z.array( inheritance: z.array(
z.object({ z.strictObject({
child: z.coerce.number(), child: z.coerce.number(),
child_source: z.coerce.number(), child_source: z.coerce.number(),
parent: z.coerce.number(), parent: z.coerce.number(),
parent_source: z.coerce.number() parent_source: z.coerce.number()
}) })
), ),
oss: z.array(z.object({ id: z.coerce.number(), alias: z.string() })) oss: z.array(z.strictObject({ id: z.coerce.number(), alias: z.string() }))
}); });
export const schemaVersionCreatedResponse = z.object({ export const schemaVersionCreatedResponse = z.strictObject({
version: z.number(), version: z.number(),
schema: schemaRSForm schema: schemaRSForm
}); });
@ -326,57 +326,57 @@ export const schemaCstCreate = schemaConstituentaBasics
insert_after: z.number().nullable() insert_after: z.number().nullable()
}); });
export const schemaCstCreatedResponse = z.object({ export const schemaCstCreatedResponse = z.strictObject({
new_cst: schemaConstituentaBasics, new_cst: schemaConstituentaBasics,
schema: schemaRSForm schema: schemaRSForm
}); });
export const schemaCstUpdate = z.object({ export const schemaCstUpdate = z.strictObject({
target: z.number(), target: z.number(),
item_data: z.object({ item_data: z.strictObject({
convention: z.string().optional(), convention: z.string().optional(),
definition_formal: z.string().optional(), definition_formal: z.string().optional(),
definition_raw: z.string().optional(), definition_raw: z.string().optional(),
term_raw: z.string().optional(), term_raw: z.string().optional(),
term_forms: z.array(z.object({ text: z.string(), tags: z.string() })).optional() term_forms: z.array(z.strictObject({ text: z.string(), tags: z.string() })).optional()
}) })
}); });
export const schemaCstRename = z.object({ export const schemaCstRename = z.strictObject({
target: z.number(), target: z.number(),
alias: z.string(), alias: z.string(),
cst_type: z.nativeEnum(CstType) cst_type: z.nativeEnum(CstType)
}); });
export const schemaProduceStructureResponse = z.object({ export const schemaProduceStructureResponse = z.strictObject({
cst_list: z.array(z.number()), cst_list: z.array(z.number()),
schema: schemaRSForm schema: schemaRSForm
}); });
export const schemaCstSubstitute = z.object({ export const schemaCstSubstitute = z.strictObject({
original: z.number(), original: z.number(),
substitution: z.number() substitution: z.number()
}); });
export const schemaCstSubstitutions = z.object({ export const schemaCstSubstitutions = z.strictObject({
substitutions: z.array(schemaCstSubstitute).min(1, { message: errorMsg.emptySubstitutions }) substitutions: z.array(schemaCstSubstitute).min(1, { message: errorMsg.emptySubstitutions })
}); });
export const schemaInlineSynthesis = z.object({ export const schemaInlineSynthesis = z.strictObject({
receiver: z.number(), receiver: z.number(),
source: z.number().nullable(), source: z.number().nullable(),
items: z.array(z.number()), items: z.array(z.number()),
substitutions: z.array(schemaCstSubstitute) substitutions: z.array(schemaCstSubstitute)
}); });
export const schemaRSErrorDescription = z.object({ export const schemaRSErrorDescription = z.strictObject({
errorType: z.nativeEnum(RSErrorType), errorType: z.nativeEnum(RSErrorType),
position: z.number(), position: z.number(),
isCritical: z.boolean(), isCritical: z.boolean(),
params: z.array(z.string()) params: z.array(z.string())
}); });
export const schemaExpressionParse = z.object({ export const schemaExpressionParse = z.strictObject({
parseResult: z.boolean(), parseResult: z.boolean(),
prefixLen: z.number(), prefixLen: z.number(),
syntax: z.nativeEnum(Syntax), syntax: z.nativeEnum(Syntax),
@ -385,17 +385,17 @@ export const schemaExpressionParse = z.object({
errors: z.array(schemaRSErrorDescription), errors: z.array(schemaRSErrorDescription),
astText: z.string(), astText: z.string(),
ast: z.array( ast: z.array(
z.object({ z.strictObject({
uid: z.number(), uid: z.number(),
parent: z.number(), parent: z.number(),
typeID: z.nativeEnum(TokenID), typeID: z.nativeEnum(TokenID),
start: z.number(), start: z.number(),
finish: z.number(), finish: z.number(),
data: z.object({ dataType: z.string(), value: z.unknown().refine(value => value !== undefined) }) data: z.strictObject({ dataType: z.string(), value: z.unknown().refine(value => value !== undefined) })
}) })
), ),
args: z.array( args: z.array(
z.object({ z.strictObject({
alias: z.string(), alias: z.string(),
typification: z.string() typification: z.string()
}) })

View File

@ -36,8 +36,11 @@ export interface IReferenceInputState {
const schemaEditReferenceState = z const schemaEditReferenceState = z
.object({ .object({
type: z.nativeEnum(ReferenceType), type: z.nativeEnum(ReferenceType),
entity: z.object({ entity: z.string(), grams: z.array(z.object({ value: z.string(), label: z.string() })) }), entity: z.strictObject({
syntactic: z.object({ offset: z.coerce.number(), nominal: z.string() }) entity: z.string(),
grams: z.array(z.strictObject({ value: z.string(), label: z.string() }))
}),
syntactic: z.strictObject({ offset: z.coerce.number(), nominal: z.string() })
}) })
.refine( .refine(
data => data =>

View File

@ -268,11 +268,11 @@ export interface ITextPosition {
finish: number; finish: number;
} }
export const schemaReference = z.object({ export const schemaReference = z.strictObject({
type: z.nativeEnum(ReferenceType), type: z.nativeEnum(ReferenceType),
data: z.union([ data: z.union([
z.object({ entity: z.string(), form: z.string() }), z.strictObject({ entity: z.string(), form: z.string() }),
z.object({ offset: z.number(), nominal: z.string() }) z.strictObject({ offset: z.number(), nominal: z.string() })
]) ])
}); });

View File

@ -19,7 +19,7 @@ import { ConstituentaTooltip } from '../../components/ConstituentaTooltip';
import { RSEditState, RSTabID } from './RSEditContext'; import { RSEditState, RSTabID } from './RSEditContext';
import { RSTabs } from './RSTabs'; import { RSTabs } from './RSTabs';
const paramsSchema = z.object({ const paramsSchema = z.strictObject({
id: z.coerce.number(), id: z.coerce.number(),
version: z.coerce version: z.coerce
.number() .number()

View File

@ -22,7 +22,7 @@ export type IUserSignupDTO = z.infer<typeof schemaUserSignup>;
export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>; export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>;
// ========= SCHEMAS ======== // ========= SCHEMAS ========
export const schemaUser = z.object({ export const schemaUser = z.strictObject({
id: z.coerce.number(), id: z.coerce.number(),
username: z.string().nonempty(errorMsg.requiredField), username: z.string().nonempty(errorMsg.requiredField),
is_staff: z.boolean(), is_staff: z.boolean(),
@ -47,7 +47,7 @@ export const schemaUserSignup = z
}) })
.refine(schema => schema.password === schema.password2, { path: ['password2'], message: errorMsg.passwordsMismatch }); .refine(schema => schema.password === schema.password2, { path: ['password2'], message: errorMsg.passwordsMismatch });
export const schemaUpdateProfile = z.object({ export const schemaUpdateProfile = z.strictObject({
email: z.string().email(errorMsg.emailField), email: z.string().email(errorMsg.emailField),
first_name: z.string(), first_name: z.string(),
last_name: z.string() last_name: z.string()

View File

@ -4,7 +4,12 @@
"skipLibCheck": true, "skipLibCheck": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true,
"baseUrl": "./src/",
"paths": {
"@/*": ["*"]
}
}, },
"include": ["vite.config.ts", "package.json", "playwright.config.ts", "tests"] "include": ["vite.config.ts", "package.json", "playwright.config.ts", "tests", "src"]
} }