diff --git a/external/lang b/external/lang
index b40f8ce53831a590c0ffdd02f8da9fd35b7a3701..ab87bdcf15d60cfb6f3bd52af445456e5fd23e5f 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit b40f8ce53831a590c0ffdd02f8da9fd35b7a3701
+Subproject commit ab87bdcf15d60cfb6f3bd52af445456e5fd23e5f
diff --git a/package.json b/package.json
index c2d8fb9d09fd43dfbd5a70a93174d30d7e2b1fe1..4a44f8bec988c958d43fd6902ae5dd322e8979a8 100644
--- a/package.json
+++ b/package.json
@@ -94,7 +94,7 @@
     "react-router-dom": "^5.2.0",
     "react-scroll": "^1.8.2",
     "redux": "^4.1.0",
-    "revolt.js": "4.3.3-alpha.17",
+    "revolt.js": "^4.3.3-alpha.18",
     "rimraf": "^3.0.2",
     "sass": "^1.35.1",
     "shade-blend-color": "^1.0.0",
diff --git a/src/components/common/messaging/MessageBase.tsx b/src/components/common/messaging/MessageBase.tsx
index 04c3d1d9e589092fe949c528bea8fff8c079f73d..a42f62fb4254c182ad06d88678d5ee65d754c978 100644
--- a/src/components/common/messaging/MessageBase.tsx
+++ b/src/components/common/messaging/MessageBase.tsx
@@ -172,9 +172,9 @@ export const MessageContent = styled.div`
     flex-grow: 1;
     display: flex;
     // overflow: hidden;
-    font-size: var(--text-size);
     flex-direction: column;
     justify-content: center;
+    font-size: var(--text-size);
 `;
 
 export const DetailBase = styled.div`
diff --git a/src/components/ui/Tip.tsx b/src/components/ui/Tip.tsx
index 23621168c77249a94db191e48ac7b84b2d3f97d2..2748cb49bc35b6f22d15f0fc2b235f196eeacc72 100644
--- a/src/components/ui/Tip.tsx
+++ b/src/components/ui/Tip.tsx
@@ -55,11 +55,13 @@ export const TipBase = styled.div<Props>`
         `}
 `;
 
-export default function Tip(props: Props & { children: Children }) {
-    const { children, ...tipProps } = props;
+export default function Tip(
+    props: Props & { children: Children; hideSeparator?: boolean },
+) {
+    const { children, hideSeparator, ...tipProps } = props;
     return (
         <>
-            <Separator />
+            {!hideSeparator && <Separator />}
             <TipBase {...tipProps}>
                 <InfoCircle size={20} />
                 <span>{props.children}</span>
diff --git a/src/pages/settings/ServerSettings.tsx b/src/pages/settings/ServerSettings.tsx
index 66c6881d40d0823c449051943cca2a9552af2374..dca442201808c16714cc7acbeebfc47fe09fc1c4 100644
--- a/src/pages/settings/ServerSettings.tsx
+++ b/src/pages/settings/ServerSettings.tsx
@@ -1,4 +1,4 @@
-import { ListUl, ListCheck } from "@styled-icons/boxicons-regular";
+import { ListUl, ListCheck, ListMinus } from "@styled-icons/boxicons-regular";
 import { XSquare, Share, Group } from "@styled-icons/boxicons-solid";
 import { Route, useHistory, useParams } from "react-router-dom";
 
@@ -11,6 +11,7 @@ import Category from "../../components/ui/Category";
 
 import { GenericSettings } from "./GenericSettings";
 import { Bans } from "./server/Bans";
+import { Categories } from "./server/Categories";
 import { Invites } from "./server/Invites";
 import { Members } from "./server/Members";
 import { Overview } from "./server/Overview";
@@ -41,6 +42,13 @@ export default function ServerSettings() {
                         <Text id="app.settings.server_pages.overview.title" />
                     ),
                 },
+                {
+                    id: "categories",
+                    icon: <ListMinus size={20} />,
+                    title: (
+                        <Text id="app.settings.server_pages.categories.title" />
+                    ),
+                },
                 {
                     id: "members",
                     icon: <Group size={20} />,
@@ -68,6 +76,9 @@ export default function ServerSettings() {
                 },
             ]}
             children={[
+                <Route path="/server/:server/settings/categories">
+                    <Categories server={server} />
+                </Route>,
                 <Route path="/server/:server/settings/members">
                     <RequiresOnline>
                         <Members server={server} />
diff --git a/src/pages/settings/channel/Permissions.tsx b/src/pages/settings/channel/Permissions.tsx
index 5a933c3268310005fd2639b43c57385e26ef1eaa..963694ae00c87ede7027fe37ff77b22429e0099d 100644
--- a/src/pages/settings/channel/Permissions.tsx
+++ b/src/pages/settings/channel/Permissions.tsx
@@ -83,8 +83,10 @@ export default function Permissions({ channel }: Props) {
                     </Checkbox>
                 );
             })}
-            <h2>channel per??issions</h2>
+            <h2>channel permissions</h2>
             {Object.keys(ChannelPermission).map((perm) => {
+                if (perm === "View") return null;
+
                 const value =
                     ChannelPermission[perm as keyof typeof ChannelPermission];
                 if (value & DEFAULT_PERMISSION_DM) {
diff --git a/src/pages/settings/server/Categories.tsx b/src/pages/settings/server/Categories.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8f35bcf17d540317d3314ae90e0aa93a63263a2c
--- /dev/null
+++ b/src/pages/settings/server/Categories.tsx
@@ -0,0 +1,153 @@
+import { XCircle } from "@styled-icons/boxicons-regular";
+import isEqual from "lodash.isequal";
+import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
+import { Route } from "revolt.js/dist/api/routes";
+import { ulid } from "ulid";
+
+import styles from "./Panes.module.scss";
+import { Text } from "preact-i18n";
+import { useContext, useEffect, useState } from "preact/hooks";
+
+import { AppContext } from "../../../context/revoltjs/RevoltClient";
+import { useChannels } from "../../../context/revoltjs/hooks";
+
+import ChannelIcon from "../../../components/common/ChannelIcon";
+import UserIcon from "../../../components/common/user/UserIcon";
+import Button from "../../../components/ui/Button";
+import ComboBox from "../../../components/ui/ComboBox";
+import IconButton from "../../../components/ui/IconButton";
+import InputBox from "../../../components/ui/InputBox";
+import Preloader from "../../../components/ui/Preloader";
+import Tip from "../../../components/ui/Tip";
+
+interface Props {
+    server: Servers.Server;
+}
+
+// ! FIXME: really bad code
+export function Categories({ server }: Props) {
+    const client = useContext(AppContext);
+    const channels = useChannels(server.channels) as (
+        | Channels.TextChannel
+        | Channels.VoiceChannel
+    )[];
+
+    const [cats, setCats] = useState<Servers.Category[]>(
+        server.categories ?? [],
+    );
+
+    const [name, setName] = useState("");
+
+    return (
+        <div>
+            <Tip warning>This section is under construction.</Tip>
+            <p>
+                <Button
+                    contrast
+                    disabled={isEqual(server.categories ?? [], cats)}
+                    onClick={() =>
+                        client.servers.edit(server._id, { categories: cats })
+                    }>
+                    save categories
+                </Button>
+            </p>
+            <h2>categories</h2>
+            {cats.map((category) => (
+                <div style={{ background: "var(--hover)" }} key={category.id}>
+                    <InputBox
+                        value={category.title}
+                        onChange={(e) =>
+                            setCats(
+                                cats.map((y) =>
+                                    y.id === category.id
+                                        ? {
+                                              ...y,
+                                              title: e.currentTarget.value,
+                                          }
+                                        : y,
+                                ),
+                            )
+                        }
+                        contrast
+                    />
+                    <Button
+                        contrast
+                        onClick={() =>
+                            setCats(cats.filter((x) => x.id !== category.id))
+                        }>
+                        delete {category.title}
+                    </Button>
+                </div>
+            ))}
+            <h2>create new</h2>
+            <p>
+                <InputBox
+                    value={name}
+                    onChange={(e) => setName(e.currentTarget.value)}
+                    contrast
+                />
+                <Button
+                    contrast
+                    onClick={() => {
+                        setName("");
+                        setCats([
+                            ...cats,
+                            {
+                                id: ulid(),
+                                title: name,
+                                channels: [],
+                            },
+                        ]);
+                    }}>
+                    create
+                </Button>
+            </p>
+            <h2>channels</h2>
+            {channels.map((channel) => {
+                return (
+                    <div
+                        style={{
+                            display: "flex",
+                            gap: "12px",
+                            alignItems: "center",
+                        }}>
+                        <div style={{ flexShrink: 0 }}>
+                            <ChannelIcon target={channel} size={24} />{" "}
+                            <span>{channel.name}</span>
+                        </div>
+                        <ComboBox
+                            style={{ flexGrow: 1 }}
+                            value={
+                                cats.find((x) =>
+                                    x.channels.includes(channel._id),
+                                )?.id ?? "none"
+                            }
+                            onChange={(e) =>
+                                setCats(
+                                    cats.map((x) => {
+                                        return {
+                                            ...x,
+                                            channels: [
+                                                ...x.channels.filter(
+                                                    (y) => y !== channel._id,
+                                                ),
+                                                ...(e.currentTarget.value ===
+                                                x.id
+                                                    ? [channel._id]
+                                                    : []),
+                                            ],
+                                        };
+                                    }),
+                                )
+                            }>
+                            <option value="none">Uncategorised</option>
+                            {cats.map((x) => (
+                                <option value={x.id}>{x.title}</option>
+                            ))}
+                        </ComboBox>
+                    </div>
+                );
+            })}
+        </div>
+    );
+}
diff --git a/src/revision.ts b/src/revision.ts
index b9bf0d03d0bfa41988fe1a0d04430ca5e880acc2..11f7128746ac3ef266c2b58975d70bf16eef9ab2 100644
--- a/src/revision.ts
+++ b/src/revision.ts
@@ -1,3 +1,4 @@
-export const REPO_URL = "https://gitlab.insrt.uk/revolt/revite/-/commit";
-export const GIT_REVISION = "__GIT_REVISION__";
-export const GIT_BRANCH = "__GIT_BRANCH__";
+export const REPO_URL: string =
+    "https://gitlab.insrt.uk/revolt/revite/-/commit";
+export const GIT_REVISION: string = "__GIT_REVISION__";
+export const GIT_BRANCH: string = "__GIT_BRANCH__";
diff --git a/ui/ui.tsx b/ui/ui.tsx
index cf8f0a7913d6d2c70eeb1eb8cc8363c29bcdf3b8..46123b3a028116e4bd3d9c30f1a7883a3ac1316d 100644
--- a/ui/ui.tsx
+++ b/ui/ui.tsx
@@ -1,41 +1,49 @@
-import { useState } from 'preact/hooks';
-import styled from 'styled-components';
-import '../src/styles/index.scss'
-import { render } from 'preact'
+import styled from "styled-components";
 
-import Theme from '../src/context/Theme';
+import "../src/styles/index.scss";
+import { render } from "preact";
+import { useState } from "preact/hooks";
+
+import Theme from "../src/context/Theme";
+
+import Banner from "../src/components/ui/Banner";
+import Button from "../src/components/ui/Button";
+import Checkbox from "../src/components/ui/Checkbox";
+import ColourSwatches from "../src/components/ui/ColourSwatches";
+import ComboBox from "../src/components/ui/ComboBox";
+import InputBox from "../src/components/ui/InputBox";
+import Overline from "../src/components/ui/Overline";
+import Radio from "../src/components/ui/Radio";
+import Tip from "../src/components/ui/Tip";
 
 export const UIDemo = styled.div`
-	gap: 12px;
-	padding: 12px;
-	display: flex;
-	flex-direction: column;
-	align-items: flex-start;
+    gap: 12px;
+    padding: 12px;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
 `;
 
-import Button from '../src/components/ui/Button';
-import Banner from '../src/components/ui/Banner';
-import Checkbox from '../src/components/ui/Checkbox';
-import ComboBox from '../src/components/ui/ComboBox';
-import InputBox from '../src/components/ui/InputBox';
-import ColourSwatches from '../src/components/ui/ColourSwatches';
-import Tip from '../src/components/ui/Tip';
-import Radio from '../src/components/ui/Radio';
-import Overline from '../src/components/ui/Overline';
-
 export function UI() {
-	let [checked, setChecked] = useState(false);
-	let [colour, setColour] = useState('#FD6671');
-	let [selected, setSelected] = useState<'a' | 'b' | 'c'>('a');
+    let [checked, setChecked] = useState(false);
+    let [colour, setColour] = useState("#FD6671");
+    let [selected, setSelected] = useState<"a" | "b" | "c">("a");
 
     return (
         <>
             <Button>Button (normal)</Button>
             <Button contrast>Button (contrast)</Button>
             <Button error>Button (error)</Button>
-            <Button contrast error>Button (contrast + error)</Button>
+            <Button contrast error>
+                Button (contrast + error)
+            </Button>
             <Banner>I am a banner!</Banner>
-            <Checkbox checked={checked} onChange={setChecked} description="ok gamer">Do you want thing??</Checkbox>
+            <Checkbox
+                checked={checked}
+                onChange={setChecked}
+                description="ok gamer">
+                Do you want thing??
+            </Checkbox>
             <ComboBox>
                 <option>Select an option.</option>
                 <option>1</option>
@@ -46,24 +54,35 @@ export function UI() {
             <InputBox placeholder="Contrast input box..." contrast />
             <InputBox value="Input box with value" />
             <InputBox value="Contrast with value" contrast />
-            <ColourSwatches value={colour} onChange={v => setColour(v)} />
-            <Tip>I am a tip! I provide valuable information.</Tip>
-            <Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
-            <Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
-            <Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
+            <ColourSwatches value={colour} onChange={(v) => setColour(v)} />
+            <Tip hideSeparator>I am a tip! I provide valuable information.</Tip>
+            <Radio checked={selected === "a"} onSelect={() => setSelected("a")}>
+                First option
+            </Radio>
+            <Radio checked={selected === "b"} onSelect={() => setSelected("b")}>
+                Second option
+            </Radio>
+            <Radio checked={selected === "c"} onSelect={() => setSelected("c")}>
+                Last option
+            </Radio>
             <Overline>Normal overline</Overline>
             <Overline type="subtle">Subtle overline</Overline>
             <Overline type="error">Error overline</Overline>
             <Overline error="with error">Normal overline</Overline>
-            <Overline type="subtle" error="with error">Subtle overline</Overline>
+            <Overline type="subtle" error="with error">
+                Subtle overline
+            </Overline>
         </>
-    )
+    );
 }
 
-render(<>
-    <Theme>
-        <UIDemo>
-            <UI />
-        </UIDemo>
-    </Theme>
-</>, document.getElementById('app')!)
+render(
+    <>
+        <Theme>
+            <UIDemo>
+                <UI />
+            </UIDemo>
+        </Theme>
+    </>,
+    document.getElementById("app")!,
+);
diff --git a/yarn.lock b/yarn.lock
index db7d5ac6214abc9601a7960530e61194ea03c853..a996624760bf99e22cd542cb2f59b0d5492a9634 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3563,10 +3563,10 @@ reusify@^1.0.4:
   resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
-revolt.js@4.3.3-alpha.17:
-  version "4.3.3-alpha.17"
-  resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.17.tgz#0745d251c695840b87e98098bcc4d67c7cc15de5"
-  integrity sha512-MjxVnkkeX5md5NxZNRS9fl06jsjcDciAxKnbZ2rkBYJofQ94tvr1CYBWvFhS/u/tAR80HAPIEjJVC9HKJDK9Fg==
+revolt.js@^4.3.3-alpha.18:
+  version "4.3.3-alpha.18"
+  resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.18.tgz#a46cef600099ea22d2f6dc8d09def7e9135839af"
+  integrity sha512-3QTgX1407bLZEkxkhUsetalUGxcogpFLiTm+mPE3T9bAKgHlTC7y6F5JgHGtmMGWxsjKCDLHgHoAllwGwXJaig==
   dependencies:
     "@insertish/mutable" "1.1.0"
     axios "^0.19.2"