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
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,24 @@ dist
app/src-tauri
chat.config.ts
chat.config.js

# Platform shells not part of the server image
desktop
mobile

# Build outputs (frontend dist is rebuilt in the build stage and copied to /app/public)
app/dist
app/storybook-static

# Dev / worktree / planning artifacts
.worktrees
.planning
.github
.vscode
.idea

# Logs and local junk
*.log
file-service.log
.DS_Store
coverage
2 changes: 1 addition & 1 deletion .github/workflows/apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

- uses: denoland/setup-deno@v2
with:
deno-version: '2.x'
deno-version: '2.6.8'

- run: deno install --allow-scripts

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v2
with:
deno-version: '2.x'
deno-version: '2.6.8'
- run: deno install --allow-scripts
- name: Install frontend dependencies
working-directory: app
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ on:
push:
tags:
- v*
workflow_call:
inputs:
version:
required: true
type: string
workflow_dispatch:

permissions:
contents: write
Expand Down Expand Up @@ -37,7 +43,7 @@ jobs:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v2
with:
deno-version: '2.x'
deno-version: '2.6.8'
- run: deno install --allow-scripts
- run: deno task migrate:tests
- run: deno task check
Expand Down Expand Up @@ -73,6 +79,9 @@ jobs:
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=raw,value=${{ inputs.version }},enable=${{ inputs.version != '' }}

- name: Build and push Docker image
id: push
Expand All @@ -85,7 +94,7 @@ jobs:
push: true
platforms: linux/amd64,linux/arm64
build-args: |
APP_VERSION=${{ github.ref_name }}
APP_VERSION=${{ inputs.version || github.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max

Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.version.outputs.result }}

steps:
- name: Extract version from labels
Expand Down Expand Up @@ -47,3 +49,10 @@ jobs:
});

core.info(`Created release ${version}`);

docker:
needs: release
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.release.outputs.version }}
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v2
with:
deno-version: '2.x'
deno-version: '2.6.8'
- run: deno install --allow-scripts
- name: Install frontend dependencies
working-directory: app
Expand Down
23 changes: 18 additions & 5 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,13 @@ Create standard story template with:
## 2. Code Cleanup

### 2.1 Type Safety (HIGH PRIORITY)
- [ ] Fix pre-existing TypeScript errors (17 errors in base)
- [ ] Remove `any` types - 20 instances in app/src
- [ ] Fix `style: any` unused prop in `ActionButton.tsx:10`
- [ ] Add proper typing to API module (`deno/api/mod.ts` - `payload: any`, `document: any`)
- [ ] Fix pre-existing TypeScript errors (16 errors in base)
- [x] Remove `any` types from backend core (serializers, bus, command/query, repos, HTTP/CLI) — PR #272
- [x] Remove `any` types from storage, encryption, config, migrate, tools modules — PR #273
- [x] Remove `any` types from API module (`deno/api/`) — PR #274
- [x] Remove `any` types from frontend — client, plugins, utils, models, components — PR #275
- [x] Remove `any` types from test files (chat.ts helper, 14 test files, users.ts) — PR #276
- [x] Fix `style: any` prop in `ActionButton.tsx` — typed as `string` (PR #275)
- [ ] Remove `@ts-ignore` comments in `deno/api/files.ts` (5+ instances)
- [ ] Standardize Props interface naming

Expand Down Expand Up @@ -288,7 +291,7 @@ Create standard story template with:
## Notes

- Created: 2026-02-06
- Last updated: 2026-02-08
- Last updated: 2026-02-09
- Progress:
- ✅ **Section 1: Storybook Cleanup - COMPLETE**
- PR #249: Config fixes
Expand All @@ -299,6 +302,16 @@ Create standard story template with:
- PR #254: Message story image fix
- 🔄 **Section 2: Code Cleanup - IN PROGRESS**
- PR #255: Dependency updates (npm packages, @std alignment, ESLint React version)
<<<<<<< HEAD
- PRs #272-276: Remove `any` types across codebase (~120 instances)
=======
- PR #272: Remove `any` types from backend core
- PR #273: Remove `any` types from storage, encryption, config, migrate, tools
<<<<<<< HEAD
- PR #274: Remove `any` types from API module
=======
>>>>>>> origin/dev
>>>>>>> origin/dev
- 🔄 **Section 3: Architecture Refactoring - IN PROGRESS**
- ✅ 3.7 Documentation: ARCHITECTURE.md, CONVENTIONS.md, 14 ADRs, CLAUDE.md
- PR #256: Architecture docs
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ FROM denoland/deno:alpine-2.6.8
RUN mv /usr/local/lib /usr/local/lib.bak \
&& mkdir /usr/local/lib \
&& apk -U upgrade \
&& apk add vips-cpp build-base vips vips-dev \
&& apk add musl-dev \
&& cp -a /usr/local/lib.bak/* /usr/local/lib/ \
&& rm -rf /usr/local/lib.bak
ENV ENVIRONMENT=production
Expand Down
4 changes: 2 additions & 2 deletions app/src/js/components/contexts/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type TooltipContextType = {
content: React.ReactNode,
parent: React.ReactNode | HTMLElement,
) => void;
hide: (p: any) => void;
hide: (p: React.ReactNode | HTMLElement) => void;
};

export const TooltipContext = createContext<TooltipContextType | undefined>(
Expand Down Expand Up @@ -67,7 +67,7 @@ export const TooltipProvider = ({ children }: TooltipContextProps) => {
[setContent, setPos, setParent],
);

const hide = useCallback((p: any) => {
const hide = useCallback((p: React.ReactNode | HTMLElement) => {
if (parent === p) {
setParent(null);
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/js/components/molecules/ActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { observer } from "mobx-react-lite";
type ActionButtonProps = {
children: React.ReactNode;
action: string;
style: any;
payload: any;
style?: string;
payload?: Record<string, unknown>;
};

export const ActionButton = observer(
Expand Down
2 changes: 1 addition & 1 deletion app/src/js/components/molecules/NavChannels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const NavChannels = observer(({ icon }: NavChannelsProps) => {
className={{ active: id === c.id }}
key={c.id}
icon={icon ?? "hash"}
badge={badges.getForChannel(c.id as any)}
badge={badges.getForChannel(String(c.id))}
onClick={() => {
if (isMobile()) {
hideSidebar();
Expand Down
10 changes: 5 additions & 5 deletions app/src/js/components/molecules/NavUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const NavUserButton = ({
const NavUserContainer = observer(
({ user, badges }: { user: User; badges: ReadReceiptsModel }) => {
const app = useApp();
const channel = app.channels.getDirect(user.id as any);
const channel = app.channels.getDirect(String(user.id));
let navigate = (_path: string) => {};
try {
navigate = useNavigate();
Expand All @@ -128,11 +128,11 @@ const NavUserContainer = observer(
return (
<NavUserButton
size={30}
user={user as any}
user={user as unknown as NavUserButtonProps["user"]}
className={{ active: id === channel?.id }}
badge={badges.getForChannel(channel?.id as any)}
badge={badges.getForChannel(channel?.id ? String(channel.id) : "")}
onClick={async () => {
const channel = await client.api.putDirectChannel(user.id as any);
const channel = await client.api.putDirectChannel(String(user.id));
if (isMobile()) {
hideSidebar();
}
Expand All @@ -152,7 +152,7 @@ export const NavUsers = observer(() => {
<SectionHeader title="users" />
{users && users.map((user) => (
<NavUserContainer
key={user.id as any}
key={String(user.id)}
user={user}
badges={app.readReceipts}
/>
Expand Down
2 changes: 1 addition & 1 deletion app/src/js/components/molecules/UserMention.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const UserMentionBase = observer(({ user }: UserMentionBaseProps) => {
data-id={user.id}
href="#"
>
<span className="name">@{user?.name || (user.id as any)}</span>
<span className="name">@{user?.name || String(user.id)}</span>
</StyledLink>
);
});
Expand Down
2 changes: 1 addition & 1 deletion app/src/js/components/organisms/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const SearchResults = observer(
<div key="bottom" id="scroll-stop" />
<MessageList
renderer={BaseRenderer}
model={threadModelWrapper as any}
model={threadModelWrapper as unknown as import("../../core/models/thread").ThreadModel}
onMessageClicked={(msg: MessageModel) => {
gotoMessage(msg);
}}
Expand Down
2 changes: 1 addition & 1 deletion app/src/js/components/pages/ErrorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const Container = styled.div`

type ErrorPageProps = {
title?: string;
debug?: any;
debug?: Record<string, unknown>;
buttons?: ("retry" | "back" | "home")[];
description?: string | string[];
};
Expand Down
5 changes: 3 additions & 2 deletions app/src/js/components/pages/Register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ export const Register = () => {
console.log(err);
setMsg(err.message);
} else {
if ((err as any)?.errorCode === "USER_ALREADY_EXISTS") {
setMsg((err as any).message);
const errObj = err as { errorCode?: string; message?: string };
if (errObj.errorCode === "USER_ALREADY_EXISTS") {
setMsg(errObj.message ?? "User already exists");
} else {
console.log(err);
setMsg("Unknown error");
Expand Down
4 changes: 3 additions & 1 deletion app/src/js/core/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class Client {
return this.api.req(...args);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(name: string, cb: (e: any) => void) {
this.api.on(name, (ev: Event) => {
if (ev instanceof CustomEvent) {
Expand All @@ -32,6 +33,7 @@ export class Client {
return this;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
on2(name: string, cb: (e: any) => void) {
const handler = (ev: Event) => {
if (ev instanceof CustomEvent) {
Expand All @@ -45,7 +47,7 @@ export class Client {
return () => this.api.off(name, handler);
}

emit(type: string, data: any) {
emit(type: string, data: unknown) {
return this.api.emit(new CustomEvent(type, { detail: data }));
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/js/core/models/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class FileModel {
async dispose() {
this.id = undefined;
this.clientId = "";
this.stream = null as any;
this.stream = null as unknown as ReadableStream;
this.status = "pending";
this.fileSize = 0;
this.fileName = "";
Expand Down Expand Up @@ -144,7 +144,7 @@ export class FilesModel {
local.patch({ status: "error", progress: 0, error: "unknown error" });
}
});
toJSON(): any {
toJSON(): Array<{ id?: string; clientId: string; fileName: string; fileSize: number; contentType: string }> {
return this.list.map((f) => ({
id: f.id,
clientId: f.clientId,
Expand Down
1 change: 1 addition & 0 deletions app/src/js/core/models/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class InputModel {
}

send = flow(function* (this: InputModel, html: HTMLElement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload: any = fromDom(html);
html.innerHTML = "";
payload.attachments = this.files.toJSON();
Expand Down
2 changes: 1 addition & 1 deletion app/src/js/core/models/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class MessageModel implements ViewMessage {
favicons: string[];
charset: string;
}[];
parsingErrors?: any[];
parsingErrors?: Array<{ message: string; nodeAttributes?: Record<string, string | null>; nodeName?: string }>;
attachments?: Array<{
id: string;
fileName: string;
Expand Down
8 changes: 4 additions & 4 deletions app/src/js/core/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import { client } from "./client.ts";

declare global {
interface Window {
Chat: any;
Chat: typeof plugins;
}
}

const registry: Record<string, Array<any>> = {};
const registry: Record<string, Array<unknown>> = {};
const plugins = {
register: (slot: string, data: any) => {
register: (slot: string, data: unknown) => {
if (typeof data === "function") {
data = data(client);
}
registry[slot] = registry[slot] || [];
[data].flat().forEach((d) => registry[slot].push(d));
},

get: (slot: string): any => registry[slot] || [],
get: (slot: string): unknown[] => registry[slot] || [],
};

window.Chat = plugins;
Expand Down
2 changes: 1 addition & 1 deletion app/src/js/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const cn = (...classes: ClassNames[]) =>
return item;
}).flat().filter(Boolean).join(" ");

export const same = (o1: any, o2: any, fields: string[]): boolean => {
export const same = (o1: Record<string, unknown>, o2: Record<string, unknown>, fields: string[]): boolean => {
return fields.every((field) => {
return o1 && o2 && o1[field] === o2[field];
});
Expand Down
Loading
Loading