diff --git a/.env b/.env
index 1270639b3e1900504ba32fa1d636255f084e2901..fd4513532a5134d9e5040b2807ac5da2b32d69a9 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,3 @@
 REACT_APP_STYLEGUIDE_URL=http://localhost:3001
 REACT_APP_API_BASE_URL=https://cf2021.pirati.cz/api
-REACT_APP_WS_BASE_URL=wss://cf2021.pirati.cz/ws/posts
+REACT_APP_WS_BASE_URL=wss://cf2021.pirati.cz/ws
diff --git a/src/App.jsx b/src/App.jsx
index 88a4ccedf659dc3412738f83e78e2f940dd7c928..caecd16687bdbb4df84c5dcdad3aa49d5b1b3c37 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -27,11 +27,22 @@ const onKeycloakEvent = (event) => {
   if (["onAuthRefreshSuccess", "onAuthSuccess"].includes(event)) {
     Sentry.setUser(keycloak.tokenParsed);
     AuthStore.update((state) => {
+      const kcRoles = keycloak.tokenParsed.roles;
+      let role = null;
+
+      if (kcRoles.includes("chairman")) {
+        role = "chairman";
+      } else if (kcRoles.includes("member")) {
+        role = "member";
+      } else {
+        role = "regp";
+      }
+
       state.isAuthenticated = true;
       state.user = {
         name: keycloak.tokenParsed.name,
         username: keycloak.tokenParsed.preferred_username,
-        groups: keycloak.tokenParsed.groups,
+        role,
         accessToken: keycloak.token,
       };
     });
diff --git a/src/actions/announcements.js b/src/actions/announcements.js
index 623725ffbd307ac254a0e117da6cb9b31214774d..9d43f8333d5aebf845313520e70ac1095f8c2616 100644
--- a/src/actions/announcements.js
+++ b/src/actions/announcements.js
@@ -1,36 +1,56 @@
-import findIndex from "lodash/findIndex";
-import remove from "lodash/remove";
-import { createAsyncAction, successResult } from "pullstate";
+import keyBy from "lodash/keyBy";
+import property from "lodash/property";
+import { createAsyncAction, errorResult, successResult } from "pullstate";
 
+import { fetch } from "api";
 import { AnnouncementStore } from "stores";
+import {
+  announcementTypeMappingRev,
+  parseRawAnnouncement,
+  syncAnnoucementItemIds,
+} from "utils";
 
-/**
- * Add new announcement.
- */
-export const addAnnouncement = createAsyncAction(
-  async ({ content }) => {
-    /** @type {CF2021.Announcement} */
-    const payload = {
-      id: "999",
-      datetime: new Date(),
-      type: "announcement",
-      content,
-      seen: true,
-    };
-
-    return successResult(payload);
+export const loadAnnouncements = createAsyncAction(
+  async () => {
+    try {
+      const resp = await fetch("/announcements");
+      const data = await resp.json();
+      return successResult(data.data);
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
   },
   {
     postActionHook: ({ result }) => {
       if (!result.error) {
+        const announcements = result.payload.map(parseRawAnnouncement);
+
         AnnouncementStore.update((state) => {
-          state.items.push(result.payload);
+          state.items = keyBy(announcements, property("id"));
+          syncAnnoucementItemIds(state);
         });
       }
     },
   }
 );
 
+/**
+ * Add new announcement.
+ */
+export const addAnnouncement = createAsyncAction(async ({ content }) => {
+  try {
+    const body = JSON.stringify({
+      content,
+      type: announcementTypeMappingRev["announcement"],
+    });
+    const resp = await fetch("/announcements", { method: "POST", body });
+    const data = await resp.json();
+    return successResult(data.data);
+  } catch (err) {
+    return errorResult([], err.toString());
+  }
+});
+
 /**
  * Delete existing announcement.
  */
@@ -40,16 +60,12 @@ export const deleteAnnouncement = createAsyncAction(
    * @param {CF2021.Announcement} item
    */
   async (item) => {
-    return successResult(item);
-  },
-  {
-    postActionHook: ({ result }) => {
-      if (!result.error) {
-        AnnouncementStore.update((state) => {
-          remove(state.items, { id: result.payload.id });
-        });
-      }
-    },
+    try {
+      await fetch(`/announcements/${item.id}`, { method: "DELETE" });
+      return successResult({ item });
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
   }
 );
 
@@ -63,18 +79,14 @@ export const updateAnnouncementContent = createAsyncAction(
    * @param {string} newContent
    */
   async ({ item, newContent }) => {
-    return successResult({ item, newContent });
-  },
-  {
-    postActionHook: ({ result }) => {
-      if (!result.error) {
-        AnnouncementStore.update((state) => {
-          const itemIdx = findIndex(state.items, {
-            id: result.payload.item.id,
-          });
-          state.items[itemIdx].content = result.payload.newContent;
-        });
-      }
-    },
+    try {
+      const body = JSON.stringify({
+        content: newContent,
+      });
+      await fetch(`/announcements/${item.id}`, { method: "PUT", body });
+      return successResult({ item, newContent });
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
   }
 );
diff --git a/src/actions/misc.js b/src/actions/misc.js
deleted file mode 100644
index ca57989d4093a06a4262e7fc3d3ca06c1231e9c1..0000000000000000000000000000000000000000
--- a/src/actions/misc.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { createAsyncAction, errorResult, successResult } from "pullstate";
-import fetch from "unfetch";
-
-import { AuthStore } from "stores";
-
-export const loadGroupMappings = createAsyncAction(
-  async () => {
-    try {
-      const resp = await fetch("https://iapi.pirati.cz/v1/groups");
-      const mappings = await resp.json();
-      return successResult(mappings);
-    } catch (err) {
-      return errorResult([], err.toString());
-    }
-  },
-  {
-    postActionHook: ({ result }) => {
-      if (!result.error) {
-        AuthStore.update((state) => {
-          state.groupMappings = result.payload;
-        });
-      }
-    },
-  }
-);
-
diff --git a/src/actions/posts.js b/src/actions/posts.js
index 35c8d196d02d894041980adb26ad478f9293c8d7..24b1adfa20b4ff79fa009e462cb4124f81209304 100644
--- a/src/actions/posts.js
+++ b/src/actions/posts.js
@@ -4,7 +4,12 @@ import { createAsyncAction, errorResult, successResult } from "pullstate";
 
 import { fetch } from "api";
 import { PostStore } from "stores";
-import { filterPosts, parseRawPost, postsTypeMappingRev } from "utils";
+import {
+  filterPosts,
+  parseRawPost,
+  postsStateMappingRev,
+  postsTypeMappingRev,
+} from "utils";
 
 export const loadPosts = createAsyncAction(
   async () => {
@@ -137,6 +142,19 @@ export const hide = createAsyncAction(
   }
 );
 
+/**
+ *
+ * @param {CF2021.ProposalPost} proposal
+ * @param {CF2021.ProposalPostState} state
+ */
+const updateProposalState = async (proposal, state) => {
+  const body = JSON.stringify({
+    state: postsStateMappingRev[state],
+  });
+  await fetch(`/posts/${proposal.id}`, { method: "PUT", body });
+  return successResult(proposal);
+};
+
 /**
  * Announce procedure proposal.
  */
@@ -144,8 +162,8 @@ export const announceProposal = createAsyncAction(
   /**
    * @param {CF2021.ProposalPost} proposal
    */
-  async (proposal) => {
-    return successResult(proposal);
+  (proposal) => {
+    return updateProposalState(proposal, "announced");
   },
   {
     shortCircuitHook: ({ args }) => {
@@ -159,13 +177,6 @@ export const announceProposal = createAsyncAction(
 
       return false;
     },
-    postActionHook: ({ result }) => {
-      if (!result.error) {
-        PostStore.update((state) => {
-          state.items[result.payload.id].state = "announced";
-        });
-      }
-    },
   }
 );
 
@@ -176,8 +187,8 @@ export const acceptProposal = createAsyncAction(
   /**
    * @param {CF2021.ProposalPost} proposal
    */
-  async (proposal) => {
-    return successResult(proposal);
+  (proposal) => {
+    return updateProposalState(proposal, "accepted");
   },
   {
     shortCircuitHook: ({ args }) => {
@@ -191,13 +202,6 @@ export const acceptProposal = createAsyncAction(
 
       return false;
     },
-    postActionHook: ({ result }) => {
-      if (!result.error) {
-        PostStore.update((state) => {
-          state.items[result.payload.id].state = "accepted";
-        });
-      }
-    },
   }
 );
 
@@ -208,8 +212,8 @@ export const rejectProposal = createAsyncAction(
   /**
    * @param {CF2021.ProposalPost} proposal
    */
-  async (proposal) => {
-    return successResult(proposal);
+  (proposal) => {
+    return updateProposalState(proposal, "rejected");
   },
   {
     shortCircuitHook: ({ args }) => {
@@ -223,12 +227,30 @@ export const rejectProposal = createAsyncAction(
 
       return false;
     },
-    postActionHook: ({ result }) => {
-      if (!result.error) {
-        PostStore.update((state) => {
-          state.items[result.payload.id].state = "rejected";
-        });
+  }
+);
+
+/**
+ * Reject procedure proposal.
+ */
+export const rejectProposalByChairman = createAsyncAction(
+  /**
+   * @param {CF2021.ProposalPost} proposal
+   */
+  (proposal) => {
+    return updateProposalState(proposal, "rejected-by-chairman");
+  },
+  {
+    shortCircuitHook: ({ args }) => {
+      if (args.type !== "procedure-proposal") {
+        return errorResult();
       }
+
+      if (!["pending", "announced"].includes(args.state)) {
+        return errorResult();
+      }
+
+      return false;
     },
   }
 );
diff --git a/src/actions/program.js b/src/actions/program.js
index 23000ee52df40c78dfdb0c402e37132a3541e50e..be1612589476f7c3da615978fdd04e14acf9bd42 100644
--- a/src/actions/program.js
+++ b/src/actions/program.js
@@ -1,9 +1,13 @@
+import keyBy from "lodash/keyBy";
 import pick from "lodash/pick";
+import property from "lodash/property";
 import { createAsyncAction, errorResult, successResult } from "pullstate";
 
 import { fetch } from "api";
 import { ProgramStore } from "stores";
 
+import { loadPosts } from "./posts";
+
 export const loadProgram = createAsyncAction(
   async () => {
     try {
@@ -33,6 +37,7 @@ export const loadProgram = createAsyncAction(
                   "description",
                   "proposer",
                 ]),
+                discussionOpened: entry.discussion_opened,
                 expectedStartAt: new Date(entry.expected_start_at),
                 expectedFinishAt: entry.expected_finish_at
                   ? new Date(entry.expected_finish_at)
@@ -45,15 +50,11 @@ export const loadProgram = createAsyncAction(
         const currentEntry = result.payload.find((entry) => entry.is_live);
 
         ProgramStore.update((state) => {
-          state.schedule = entries;
+          state.items = keyBy(entries, property("id"));
+          state.scheduleIds = entries.map((entry) => entry.id);
 
           if (currentEntry) {
-            state.current = state.schedule.find(
-              (scheduleEntry) => scheduleEntry.id === currentEntry.id
-            );
-          } else {
-            // TODO: for testing only
-            state.current = state.schedule[1];
+            state.currentId = currentEntry.id;
           }
         });
       }
@@ -62,18 +63,92 @@ export const loadProgram = createAsyncAction(
 );
 
 /**
- * Open discussion.
+ * Rename program point.
+ */
+export const renameProgramPoint = createAsyncAction(
+  async ({ programEntry, newTitle }) => {
+    try {
+      const body = JSON.stringify({
+        title: newTitle,
+      });
+      await fetch(`/program/${programEntry.id}`, { method: "PUT", body });
+      return successResult({ programEntry, newTitle });
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
+  },
+  {
+    postActionHook: ({ result }) => {
+      if (!result.error) {
+        ProgramStore.update((state) => {
+          if (state.items[result.payload.programEntry.id]) {
+            state.items[result.payload.programEntry.id].title =
+              result.payload.newTitle;
+          }
+        });
+      }
+    },
+  }
+);
+
+/**
+ * End program point.
  */
 export const endProgramPoint = createAsyncAction(
-  async () => {
-    return successResult();
+  /**
+   *
+   * @param {CF2021.ProgramScheduleEntry} programEntry
+   */
+  async (programEntry) => {
+    try {
+      const body = JSON.stringify({
+        is_live: false,
+      });
+      await fetch(`/program/${programEntry.id}`, { method: "PUT", body });
+      return successResult(programEntry);
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
   },
   {
     postActionHook: ({ result }) => {
       if (!result.error) {
         ProgramStore.update((state) => {
-          state.current = null;
+          state.currentId = null;
+        });
+      }
+    },
+  }
+);
+
+/**
+ * Activate program point.
+ */
+export const activateProgramPoint = createAsyncAction(
+  /**
+   *
+   * @param {CF2021.ProgramScheduleEntry} programEntry
+   */
+  async (programEntry) => {
+    try {
+      const body = JSON.stringify({
+        is_live: true,
+      });
+      await fetch(`/program/${programEntry.id}`, { method: "PUT", body });
+      return successResult(programEntry);
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
+  },
+  {
+    postActionHook: async ({ result }) => {
+      if (!result.error) {
+        ProgramStore.update((state) => {
+          state.currentId = result.payload.id;
         });
+
+        // Re-load posts - these are bound directly to the program schedule entry.
+        loadPosts.run({}, { respectCache: false });
       }
     },
   }
@@ -83,14 +158,28 @@ export const endProgramPoint = createAsyncAction(
  * Open discussion.
  */
 export const openDiscussion = createAsyncAction(
-  async () => {
-    return successResult();
+  /**
+   *
+   * @param {CF2021.ProgramScheduleEntry} programEntry
+   */
+  async (programEntry) => {
+    try {
+      const body = JSON.stringify({
+        discussion_opened: true,
+      });
+      await fetch(`/program/${programEntry.id}`, { method: "PUT", body });
+      return successResult(programEntry);
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
   },
   {
     postActionHook: ({ result }) => {
       if (!result.error) {
         ProgramStore.update((state) => {
-          state.current.discussionOpened = true;
+          if (state.items[result.payload.id]) {
+            state.items[result.payload.id].discussionOpened = true;
+          }
         });
       }
     },
@@ -101,14 +190,24 @@ export const openDiscussion = createAsyncAction(
  * Close discussion.
  */
 export const closeDiscussion = createAsyncAction(
-  async () => {
-    return successResult();
+  async (programEntry) => {
+    try {
+      const body = JSON.stringify({
+        discussion_opened: false,
+      });
+      await fetch(`/program/${programEntry.id}`, { method: "PUT", body });
+      return successResult(programEntry);
+    } catch (err) {
+      return errorResult([], err.toString());
+    }
   },
   {
     postActionHook: ({ result }) => {
       if (!result.error) {
         ProgramStore.update((state) => {
-          state.current.discussionOpened = false;
+          if (state.items[result.payload.id]) {
+            state.items[result.payload.id].discussionOpened = false;
+          }
         });
       }
     },
diff --git a/src/actions/ws.js b/src/actions/ws.js
index 59b5c6e7f0cbf60a3ff1743cff82e314e31dfd4a..59150d3efe2c9ca2b46d0968aa5621a2c5f73f1a 100644
--- a/src/actions/ws.js
+++ b/src/actions/ws.js
@@ -2,6 +2,7 @@ import { createAsyncAction, errorResult, successResult } from "pullstate";
 
 import { connect } from "ws/connection";
 
+import { loadAnnouncements } from "./announcements";
 import { loadPosts } from "./posts";
 import { loadProgram } from "./program";
 
@@ -13,6 +14,7 @@ export const initializeWSChannel = createAsyncAction(async () => {
         // any intermediate state.
         await Promise.all([
           loadProgram.run({}, { respectCache: false }),
+          loadAnnouncements.run({}, { respectCache: false }),
           loadPosts.run({}, { respectCache: false }),
         ]);
 
diff --git a/src/api.js b/src/api.js
index 707c7c2af86436cb7fd27976829f1dc7d08f9f2e..c7d5efd34840b6f275fe913ab5c692ccf527a548 100644
--- a/src/api.js
+++ b/src/api.js
@@ -12,5 +12,9 @@ export const fetch = (url, opts) => {
     opts.headers.Authorization = "Bearer " + user.accessToken;
   }
 
+  if (!opts.headers["Content-Type"]) {
+    opts.headers["Content-Type"] = "application/json";
+  }
+
   return baseFetch(process.env.REACT_APP_API_BASE_URL + url, opts);
 };
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 28d2d9f1e807c6629c133e19648fe71f9d95ed9f..e2ce840a7441f89e32d3c63a06efca87b04fa848 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -48,6 +48,11 @@ const Navbar = () => {
           <>
             <div className="navbar__main navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto">
               <ul className="navbar-menu text-white">
+                <li className="navbar-menu__item">
+                  <NavLink className="navbar-menu__link" to="/">
+                    Přímý přenos
+                  </NavLink>
+                </li>
                 <li className="navbar-menu__item">
                   <NavLink className="navbar-menu__link" to="/program">
                     Program
diff --git a/src/components/Thumbs.jsx b/src/components/Thumbs.jsx
index 6f23ccecdfaca755e6a4b1449a3b1ae5c6620f51..8f39694e3d187ffabb4cd235994eb7e893668f19 100644
--- a/src/components/Thumbs.jsx
+++ b/src/components/Thumbs.jsx
@@ -1,18 +1,27 @@
 import React from "react";
+import classNames from "classnames";
 
-const Thumbs = ({ likes, dislikes, onLike, onDislike, myVote }) => {
+const Thumbs = ({ likes, dislikes, onLike, onDislike, readOnly }) => {
   return (
     <div>
       <div className="space-x-2 text-sm flex items-center">
         <button
-          className="text-blue-300 flex items-center space-x-1 cursor-pointer"
+          className={classNames("text-blue-300 flex items-center space-x-1", {
+            "cursor-pointer": !readOnly,
+            "cursor-default": readOnly,
+          })}
+          disabled={readOnly}
           onClick={onLike}
         >
           <span className="font-bold">{likes}</span>
           <i className="ico--thumbs-up"></i>
         </button>
         <button
-          className="text-red-600 flex items-center space-x-1 cursor-pointer"
+          className={classNames("text-red-600 flex items-center space-x-1", {
+            "cursor-pointer": !readOnly,
+            "cursor-default": readOnly,
+          })}
+          disabled={readOnly}
           onClick={onDislike}
         >
           <i className="ico--thumbs-down transform -scale-x-1"></i>
diff --git a/src/components/annoucements/Announcement.jsx b/src/components/annoucements/Announcement.jsx
index 34a39025f6b3c9849d75c853894344770fe91ac0..2d6c72aa8dd70bdb662be43e8b44627c8309195e 100644
--- a/src/components/annoucements/Announcement.jsx
+++ b/src/components/annoucements/Announcement.jsx
@@ -14,7 +14,7 @@ const Announcement = ({
   link,
   relatedPostId,
   seen,
-  displayActions = false,
+  canRunActions,
   onDelete,
   onEdit,
   onSeen,
@@ -85,7 +85,7 @@ const Announcement = ({
           </Chip>
           {link && <a href={link}>{linkLabel + "»"}</a>}
         </div>
-        {displayActions && (
+        {canRunActions && (
           <DropdownMenu right triggerIconClass="ico--dots-three-horizontal">
             {showEdit && (
               <DropdownMenuItem
diff --git a/src/components/annoucements/AnnouncementEditModal.jsx b/src/components/annoucements/AnnouncementEditModal.jsx
index 57d96813768d77200be32261db2cfd1115d0b8cb..df5d583f03c86f6f4c33377138355f9a5a36f281 100644
--- a/src/components/annoucements/AnnouncementEditModal.jsx
+++ b/src/components/annoucements/AnnouncementEditModal.jsx
@@ -33,6 +33,9 @@ const AnnouncementEditModal = ({
             </button>
           </div>
           <div className="form-field">
+            <label className="form-field__label" htmlFor="field">
+              Nový text oznámení
+            </label>
             <div className="form-field__wrapper form-field__wrapper--shadowed">
               <textarea
                 className="text-input form-field__control text-base"
diff --git a/src/components/annoucements/AnnouncementList.jsx b/src/components/annoucements/AnnouncementList.jsx
index 693cf9d1e9258a55a8b6a4d57f7c306da2faee47..c275a6532437b82bcd0d13415e7b67c0589c2256 100644
--- a/src/components/annoucements/AnnouncementList.jsx
+++ b/src/components/annoucements/AnnouncementList.jsx
@@ -6,7 +6,7 @@ import Announcement from "./Announcement";
 const AnnouncementList = ({
   items,
   className,
-  displayActions,
+  canRunActions,
   onDelete,
   onEdit,
   onSeen,
@@ -33,7 +33,7 @@ const AnnouncementList = ({
           content={item.content}
           link={item.link}
           seen={item.seen}
-          displayActions={displayActions}
+          canRunActions={canRunActions}
           onEdit={onAnnouncementEdit(item)}
           onDelete={onAnnouncementDelete(item)}
           onSeen={onAnnouncementSeen(item)}
diff --git a/src/components/posts/Post.jsx b/src/components/posts/Post.jsx
index d78acdbb3f52903fd178b9e8113a624369369def..5836b184a3b2cbf72f54c1b0321180fc29b81e82 100644
--- a/src/components/posts/Post.jsx
+++ b/src/components/posts/Post.jsx
@@ -19,7 +19,8 @@ const Post = ({
   archived,
   state,
   dimIfArchived = true,
-  displayActions = false,
+  canThumb,
+  canRunActions,
   onLike,
   onDislike,
   onHide,
@@ -27,6 +28,7 @@ const Post = ({
   onAnnounceProcedureProposal,
   onAcceptProcedureProposal,
   onRejectProcedureProposal,
+  onRejectProcedureProposalByChairman,
   onSeen,
 }) => {
   const { ref, inView } = useInView({
@@ -124,6 +126,8 @@ const Post = ({
     type === "procedure-proposal" && state === "announced";
   const showRejectAction =
     type === "procedure-proposal" && state === "announced";
+  const showRejectByChairmanAction =
+    type === "procedure-proposal" && ["announced", "pending"].includes(state);
   const showBanAction = true;
   const showHideAction = !archived;
 
@@ -160,11 +164,12 @@ const Post = ({
               <Thumbs
                 likes={ranking.likes}
                 dislikes={ranking.dislikes}
+                readOnly={!canThumb}
                 onLike={onLike}
                 onDislike={onDislike}
                 myVote={ranking.myVote}
               />
-              {displayActions && (
+              {canRunActions && (
                 <DropdownMenu right>
                   {showAnnounceAction && (
                     <DropdownMenuItem
@@ -187,6 +192,13 @@ const Post = ({
                       title="Zamítnout procedurální návrh"
                     />
                   )}
+                  {showRejectByChairmanAction && (
+                    <DropdownMenuItem
+                      onClick={onRejectProcedureProposalByChairman}
+                      icon="ico--thumbs-down"
+                      title="Zamítnout procedurální návrh předsedajícím"
+                    />
+                  )}
                   {showBanAction && (
                     <DropdownMenuItem
                       onClick={onBanUser}
diff --git a/src/components/posts/PostList.jsx b/src/components/posts/PostList.jsx
index 0984dfa7033921f64bb0584af48cf014275db85d..94e553541e0e473542874919867355e60a866a33 100644
--- a/src/components/posts/PostList.jsx
+++ b/src/components/posts/PostList.jsx
@@ -6,6 +6,8 @@ import Post from "./Post";
 const PostList = ({
   className,
   items,
+  canThumb,
+  canRunActions,
   onLike,
   onDislike,
   onHide,
@@ -13,6 +15,7 @@ const PostList = ({
   onAnnounceProcedureProposal,
   onAcceptProcedureProposal,
   onRejectProcedureProposal,
+  onRejectProcedureProposalByChairman,
   onSeen,
   dimArchived,
 }) => {
@@ -30,6 +33,9 @@ const PostList = ({
   );
   const onPostAcceptProcedureProposal = buildHandler(onAcceptProcedureProposal);
   const onPostRejectProcedureProposal = buildHandler(onRejectProcedureProposal);
+  const onPostRejectProcedureProposalByChairman = buildHandler(
+    onRejectProcedureProposalByChairman
+  );
 
   const onPostSeen = (post) => () => {
     onSeen(post);
@@ -52,8 +58,9 @@ const PostList = ({
             modified={item.modified}
             seen={item.seen}
             archived={item.archived}
-            displayActions={true}
             dimIfArchived={dimArchived}
+            canThumb={canThumb}
+            canRunActions={canRunActions}
             onLike={onPostLike(item)}
             onDislike={onPostDislike(item)}
             onHide={onPostHide(item)}
@@ -61,6 +68,9 @@ const PostList = ({
             onAnnounceProcedureProposal={onPostAnnounceProcedureProposal(item)}
             onAcceptProcedureProposal={onPostAcceptProcedureProposal(item)}
             onRejectProcedureProposal={onPostRejectProcedureProposal(item)}
+            onRejectProcedureProposalByChairman={onPostRejectProcedureProposalByChairman(
+              item
+            )}
             onSeen={onPostSeen(item)}
           />
         ))}
diff --git a/src/components/program/ProgramEntryEditModal.jsx b/src/components/program/ProgramEntryEditModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..90b5385514fa5d3424277ad6d8865334b52b64e3
--- /dev/null
+++ b/src/components/program/ProgramEntryEditModal.jsx
@@ -0,0 +1,73 @@
+import React, { useState } from "react";
+
+import Button from "components/Button";
+import { Card, CardActions, CardBody, CardHeadline } from "components/cards";
+import Modal from "components/modals/Modal";
+
+const ProgramEntryEditModal = ({
+  programEntry,
+  onCancel,
+  onConfirm,
+  ...props
+}) => {
+  const [text, setText] = useState(programEntry.title);
+
+  const onTextInput = (evt) => {
+    setText(evt.target.value);
+  };
+
+  const confirm = (evt) => {
+    if (!!text) {
+      onConfirm(text);
+    }
+  };
+
+  return (
+    <Modal containerClassName="max-w-md" onRequestClose={onCancel} {...props}>
+      <Card>
+        <CardBody>
+          <div className="flex items-center justify-between mb-4">
+            <CardHeadline>Upravit název programového bodu</CardHeadline>
+            <button onClick={onCancel}>
+              <i className="ico--close"></i>
+            </button>
+          </div>
+          <div className="form-field">
+            <label className="form-field__label" htmlFor="field">
+              Nový název
+            </label>
+            <div className="form-field__wrapper form-field__wrapper--shadowed">
+              <input
+                type="text"
+                className="text-input form-field__control"
+                value={text}
+                onChange={onTextInput}
+                placeholder="Vyplňte nový název"
+              />
+            </div>
+          </div>
+        </CardBody>
+        <CardActions right className="space-x-1">
+          <Button
+            hoverActive
+            color="blue-300"
+            className="text-sm"
+            onClick={confirm}
+          >
+            Uložit
+          </Button>
+          <Button
+            hoverActive
+            color="red-600"
+            className="text-sm"
+            onClick={onCancel}
+          >
+            Zrušit
+          </Button>
+        </CardActions>
+      </Card>
+    </Modal>
+  );
+};
+
+export default ProgramEntryEditModal;
diff --git a/src/containers/AnnoucementsContainer.jsx b/src/containers/AnnoucementsContainer.jsx
index 4cd97890d549e6c93191fb8bd4999f96ea16c385..1a2318ab73115b668edaf51f09f006a251696dd4 100644
--- a/src/containers/AnnoucementsContainer.jsx
+++ b/src/containers/AnnoucementsContainer.jsx
@@ -8,8 +8,7 @@ import AnnouncementEditModal from "components/annoucements/AnnouncementEditModal
 import AnnouncementList from "components/annoucements/AnnouncementList";
 import ModalConfirm from "components/modals/ModalConfirm";
 import { useItemActionConfirm } from "hooks";
-import { AnnouncementStore } from "stores";
-import findIndex from "lodash/findIndex";
+import { AnnouncementStore, AuthStore } from "stores";
 
 const AnnoucementsContainer = () => {
   const [itemToEdit, setItemToEdit] = useState(null);
@@ -21,7 +20,10 @@ const AnnoucementsContainer = () => {
     onDeleteCancel,
   ] = useItemActionConfirm(deleteAnnouncement);
 
-  const items = AnnouncementStore.useState((state) => state.items);
+  const { isAuthenticated, user } = AuthStore.useState();
+  const items = AnnouncementStore.useState((state) =>
+    state.itemIds.map((id) => state.items[id])
+  );
 
   const confirmEdit = useCallback(
     async (newContent) => {
@@ -38,13 +40,12 @@ const AnnoucementsContainer = () => {
   }, [setItemToEdit]);
 
   /**
-   * Mark down user saw this post already.
-   * @param {CF2021.Announcement} post
+   * Mark down user saw this announcement already.
+   * @param {CF2021.Announcement} announcement
    */
   const markSeen = (announcement) => {
     AnnouncementStore.update((state) => {
-      const idx = findIndex(state.items, announcement);
-      state.items[idx].seen = true;
+      state.items[announcement.id].seen = true;
     });
   };
 
@@ -52,7 +53,7 @@ const AnnoucementsContainer = () => {
     <>
       <AnnouncementList
         items={items}
-        displayActions={true}
+        canRunActions={isAuthenticated && user.role === "chairman"}
         onDelete={setItemToDelete}
         onEdit={setItemToEdit}
         onSeen={markSeen}
@@ -61,10 +62,10 @@ const AnnoucementsContainer = () => {
         isOpen={!!itemToDelete}
         onConfirm={onDeleteConfirm}
         onCancel={onDeleteCancel}
-        title="Opravdu chcete toto oznámení smazat?"
+        title="Opravdu smazat?"
         yesActionLabel="Smazat"
       >
-        Opravdu chcete ukončit rozpravu?
+        Tato akce je nevratná. Opravdu chcete toto oznámení smazat?
       </ModalConfirm>
       {itemToEdit && (
         <AnnouncementEditModal
diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx
index 5f7a01a17f2670f933d07e42d9d22213a3ebb637..c0ebf21a5163c680a09ea69172b2f696fd73172b 100644
--- a/src/containers/PostsContainer.jsx
+++ b/src/containers/PostsContainer.jsx
@@ -8,12 +8,13 @@ import {
   hide,
   like,
   rejectProposal,
+  rejectProposalByChairman,
 } from "actions/posts";
 import { ban } from "actions/users";
 import ModalConfirm from "components/modals/ModalConfirm";
 import PostList from "components/posts/PostList";
 import { useItemActionConfirm } from "hooks";
-import { PostStore } from "stores";
+import { AuthStore, PostStore } from "stores";
 
 const PostsContainer = ({ className }) => {
   const [
@@ -46,7 +47,14 @@ const PostsContainer = ({ className }) => {
     onRejectConfirm,
     onRejectCancel,
   ] = useItemActionConfirm(rejectProposal);
+  const [
+    postToRejectByChairman,
+    setPostToRejectByChairman,
+    onRejectByChairmanConfirm,
+    onRejectByChairmanCancel,
+  ] = useItemActionConfirm(rejectProposalByChairman);
 
+  const { isAuthenticated, user } = AuthStore.useState();
   const { window, items } = PostStore.useState((state) =>
     pick(state, ["window", "items"])
   );
@@ -81,17 +89,19 @@ const PostsContainer = ({ className }) => {
         items={window.items
           .slice(sliceStart, sliceEnd)
           .map((postId) => items[postId])}
+        canThumb={isAuthenticated}
+        canRunActions={isAuthenticated && user.role === "chairman"}
         onLike={like.run}
         onDislike={dislike.run}
         onSeen={markSeen}
         className={className}
         dimArchived={!showingArchivedOnly}
-        displayActions={true}
         onHide={setPostToHide}
         onBanUser={onBanUser}
         onAnnounceProcedureProposal={setPostToAnnounce}
         onAcceptProcedureProposal={setPostToAccept}
         onRejectProcedureProposal={setPostToReject}
+        onRejectProcedureProposalByChairman={setPostToRejectByChairman}
       />
       <ModalConfirm
         isOpen={!!userToBan}
@@ -148,6 +158,16 @@ const PostsContainer = ({ className }) => {
       >
         Procedurální návrh bude <strong>zamítnut</strong>. Opravdu to chcete?
       </ModalConfirm>
+      <ModalConfirm
+        isOpen={!!postToRejectByChairman}
+        onConfirm={onRejectByChairmanConfirm}
+        onCancel={onRejectByChairmanCancel}
+        title="Zamítnout procedurální návrh předsedajícícm?"
+        yesActionLabel="Zamítnout návrh předsedajícím"
+      >
+        Procedurální návrh bude <strong>zamítnut předsedajícím</strong>. Opravdu
+        to chcete?
+      </ModalConfirm>
     </>
   );
 };
diff --git a/src/hooks.js b/src/hooks.js
index f138fe47126663faeb7d5cb36f5902fb8459307c..31703eb9b35e5513dd24b602ee8445ef8605a729 100644
--- a/src/hooks.js
+++ b/src/hooks.js
@@ -17,15 +17,15 @@ export const useItemActionConfirm = (actionFn) => {
   return [item, setItem, onActionConfirm, onActionCancel];
 };
 
-export const useActionConfirm = (actionFn) => {
+export const useActionConfirm = (actionFn, actionArgs) => {
   const [showConfirm, setShowConfirm] = useState(false);
 
   const onActionConfirm = useCallback(() => {
     if (showConfirm) {
-      actionFn.run();
+      actionFn.run(actionArgs);
       setShowConfirm(false);
     }
-  }, [showConfirm, setShowConfirm, actionFn]);
+  }, [showConfirm, setShowConfirm, actionFn, actionArgs]);
 
   const onActionCancel = useCallback(() => {
     setShowConfirm(false);
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
index 4b0c764ac12d6a757aee3fb72151feb4a8942817..0e7c7a438559961cba97aa5060f808966db690bf 100644
--- a/src/pages/Home.jsx
+++ b/src/pages/Home.jsx
@@ -1,22 +1,24 @@
-import React from "react";
+import React, { useState } from "react";
 
 import {
   closeDiscussion,
   endProgramPoint,
   openDiscussion,
+  renameProgramPoint,
 } from "actions/program";
 import Button from "components/Button";
 import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu";
 import ModalConfirm from "components/modals/ModalConfirm";
+import ProgramEntryEditModal from "components/program/ProgramEntryEditModal";
 import AddAnnouncementForm from "containers/AddAnnouncementForm";
 import AddPostForm from "containers/AddPostForm";
 import AnnouncementsContainer from "containers/AnnoucementsContainer";
 import PostFilters from "containers/PostFilters";
 import PostsContainer from "containers/PostsContainer";
 import { useActionConfirm } from "hooks";
-import { ProgramStore } from "stores";
+import { AuthStore, ProgramStore } from "stores";
 
-const noCurrentDiscussion = (
+const noprogramEntryDiscussion = (
   <article className="container container--wide pt-8 py-8 lg:py-32">
     <div className="hidden md:inline-block flag bg-violet-400 text-white head-alt-base mb-4 py-4 px-5">
       Jejda ...
@@ -35,18 +37,22 @@ const noCurrentDiscussion = (
 );
 
 const Home = () => {
+  const { currentId, items } = ProgramStore.useState();
+  const { isAuthenticated, user } = AuthStore.useState();
+  const programEntry = currentId ? items[currentId] : null;
+  const [showProgramEditModal, setShowProgramEditModal] = useState(false);
   const [
     showCloseDiscussion,
     setShowCloseDiscussion,
     onCloseDiscussionConfirm,
     onCloseDiscussionCancel,
-  ] = useActionConfirm(closeDiscussion);
+  ] = useActionConfirm(closeDiscussion, programEntry);
   const [
     showOpenDiscussion,
     setShowOpenDiscussion,
     onOpenDiscussionConfirm,
     onOpenDiscussionCancel,
-  ] = useActionConfirm(openDiscussion);
+  ] = useActionConfirm(openDiscussion, programEntry);
   const [
     showEndProgramPoint,
     setShowEndProgramPoint,
@@ -54,14 +60,16 @@ const Home = () => {
     onEndProgramPointCancel,
   ] = useActionConfirm(endProgramPoint);
 
-  const { current } = ProgramStore.useState();
-
-  const onRenameProgramPoint = () => {
-    console.log("renameProgramPoint");
+  const onEditProgramConfirm = async (newTitle) => {
+    await renameProgramPoint.run({ programEntry, newTitle });
+    setShowProgramEditModal(false);
+  };
+  const onEditProgramCancel = () => {
+    setShowProgramEditModal(false);
   };
 
-  if (!current) {
-    return noCurrentDiscussion;
+  if (!programEntry) {
+    return noprogramEntryDiscussion;
   }
 
   return (
@@ -70,17 +78,17 @@ const Home = () => {
         <section className="cf2021__video space-y-8">
           <div className="flex items-center justify-between mb-4 lg:mb-8">
             <h1 className="head-alt-md lg:head-alt-lg mb-0">
-              Bod č. {current.number}: {current.title}
+              Bod č. {programEntry.number}: {programEntry.title}
             </h1>
             <DropdownMenu right triggerSize="lg">
               <DropdownMenuItem
-                onClick={onRenameProgramPoint}
+                onClick={() => setShowProgramEditModal(true)}
                 icon="ico--edit-pencil"
                 title="Přejmenovat bod programu"
                 titleSize="base"
                 iconSize="base"
               />
-              {current.discussionOpened && (
+              {programEntry.discussionOpened && (
                 <DropdownMenuItem
                   onClick={() => setShowCloseDiscussion(true)}
                   icon="ico--bubbles"
@@ -89,7 +97,7 @@ const Home = () => {
                   iconSize="base"
                 />
               )}
-              {!current.discussionOpened && (
+              {!programEntry.discussionOpened && (
                 <DropdownMenuItem
                   onClick={() => setShowOpenDiscussion(true)}
                   icon="ico--bubbles"
@@ -127,7 +135,9 @@ const Home = () => {
             </div>
 
             <AnnouncementsContainer className="container-padding--zero lg:container-padding--auto" />
-            <AddAnnouncementForm className="lg:card__body pt-4 lg:py-6" />
+            {isAuthenticated && user.role === "chairman" && (
+              <AddAnnouncementForm className="lg:card__body pt-4 lg:py-6" />
+            )}
           </div>
         </section>
 
@@ -140,11 +150,17 @@ const Home = () => {
           </div>
 
           <PostsContainer className="container-padding--zero lg:container-padding--auto" />
-          {current.discussionOpened && (
+          {programEntry.discussionOpened && isAuthenticated && (
             <AddPostForm className="my-8 space-y-4" />
           )}
         </section>
       </article>
+      <ProgramEntryEditModal
+        isOpen={showProgramEditModal}
+        onConfirm={onEditProgramConfirm}
+        onCancel={onEditProgramCancel}
+        programEntry={programEntry}
+      />
       <ModalConfirm
         isOpen={showCloseDiscussion}
         onConfirm={onCloseDiscussionConfirm}
diff --git a/src/pages/Program.jsx b/src/pages/Program.jsx
index bf31cf18ac699a07fef0e349f2041df32f1329b8..93ab83c9edd5854bbda18a54118f4de7d3e0ead8 100644
--- a/src/pages/Program.jsx
+++ b/src/pages/Program.jsx
@@ -3,17 +3,30 @@ import { Link } from "react-router-dom";
 import classNames from "classnames";
 import { format } from "date-fns";
 
+import { activateProgramPoint } from "actions/program";
+import Button from "components/Button";
 import Chip from "components/Chip";
-import { ProgramStore } from "stores";
+import ModalConfirm from "components/modals/ModalConfirm";
+import { useItemActionConfirm } from "hooks";
+import { AuthStore, ProgramStore } from "stores";
 
 const Schedule = () => {
-  const { current, schedule } = ProgramStore.useState();
+  const { isAuthenticated, user } = AuthStore.useState();
+  const { currentId, scheduleIds, items } = ProgramStore.useState();
+  const [
+    entryToActivate,
+    setEntryToActivate,
+    onActivateConfirm,
+    onActivateCancel,
+  ] = useItemActionConfirm(activateProgramPoint);
+
   return (
     <article className="container container--wide py-8 lg:py-24">
       <h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedání</h1>
       <div className="flex flex-col">
-        {schedule.map((entry) => {
-          const isCurrent = entry === current;
+        {scheduleIds.map((id) => {
+          const isCurrent = id === currentId;
+          const entry = items[id];
           return (
             <div
               className={classNames(
@@ -44,11 +57,35 @@ const Schedule = () => {
                   <span>{entry.proposer}</span>
                 </div>
                 {entry.description && <p>{entry.description}</p>}
+                {isAuthenticated &&
+                  user.role === "chairman" &&
+                  entry.id !== currentId && (
+                    <div className="mt-4">
+                      <Button
+                        onClick={() => setEntryToActivate(entry)}
+                        color="grey-125"
+                        className="text-xs"
+                      >
+                        Aktivovat tento bod programu
+                      </Button>
+                    </div>
+                  )}
               </div>
             </div>
           );
         })}
       </div>
+      <ModalConfirm
+        isOpen={!!entryToActivate}
+        onConfirm={onActivateConfirm}
+        onCancel={onActivateCancel}
+        title="Aktivovat bod programu?"
+        yesActionLabel="Aktivovat"
+      >
+        Pogramovaný bod{" "}
+        <strong>{entryToActivate && entryToActivate.title}</strong> bude
+        aktivován. Chcete pokračovat?
+      </ModalConfirm>
     </article>
   );
 };
diff --git a/src/stores.js b/src/stores.js
index 95586eff6d37b3311416bbf06c0572bbc01171c9..aff0f1bddee9ac1ce96dfc89b9699f5879f38b70 100644
--- a/src/stores.js
+++ b/src/stores.js
@@ -4,76 +4,27 @@ import { Store } from "pullstate";
 /** @type {CF2021.AuthStorePayload} */
 const authStoreInitial = {
   isAuthenticated: false,
-  groupMappings: [],
 };
 
 export const AuthStore = new Store(authStoreInitial);
 
 /** @type {CF2021.ProgramStorePayload} */
 const programStoreInitial = {
-  current: undefined,
-  schedule: [],
+  items: {},
+  currentId: undefined,
+  scheduleIds: [],
 };
 
 export const ProgramStore = new Store(programStoreInitial);
 
 /** @type {CF2021.AnnouncementStorePayload} */
 const announcementStoreInitial = {
-  items: [
-    {
-      id: "1",
-      content:
-        "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
-      datetime: new Date(),
-      seen: false,
-      type: "rejected-procedure-proposal",
-    },
-    {
-      id: "2",
-      content:
-        "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
-      datetime: new Date(),
-      seen: false,
-      type: "accepted-procedure-proposal",
-    },
-    {
-      id: "3",
-      content:
-        "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
-      datetime: new Date(),
-      seen: true,
-      type: "suggested-procedure-proposal",
-    },
-    {
-      id: "4",
-      content:
-        "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
-      datetime: new Date(),
-      seen: true,
-      type: "voting",
-    },
-    {
-      id: "5",
-      content:
-        "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
-      datetime: new Date(),
-      seen: true,
-      type: "announcement",
-    },
-    {
-      id: "6",
-      content:
-        "Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
-      datetime: new Date(),
-      seen: true,
-      type: "user-ban",
-    },
-  ],
+  items: {},
+  itemIds: [],
 };
 
 export const AnnouncementStore = new Store(announcementStoreInitial);
 
-
 /** @type {CF2021.PostStorePayload} */
 const postStoreInitial = {
   items: {},
diff --git a/src/utils.js b/src/utils.js
index 7aab9151b7930304d56054c65e910379f63322c2..65d16be18e5e56c2511ad9ef4788049dafa1c77c 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,7 +1,7 @@
 import filter from "lodash/filter";
+import pick from "lodash/pick";
 import property from "lodash/property";
 import values from "lodash/values";
-import pick from "lodash/pick";
 
 /**
  * Filter & sort collection of posts.
@@ -45,6 +45,16 @@ export const updateWindowPosts = (state) => {
   );
 };
 
+/**
+ * Update itemIds from items.
+ * @param {CF2021.AnnouncementStorePayload} state
+ */
+export const syncAnnoucementItemIds = (state) => {
+  state.itemIds = values(state.items)
+    .sort((a, b) => b.datetime - a.datetime)
+    .map((announcement) => announcement.id);
+};
+
 export const postsMyVoteMapping = {
   0: "none",
   1: "like",
@@ -69,6 +79,32 @@ export const postsStateMapping = {
   4: "rejected-by-chairman",
 };
 
+export const postsStateMappingRev = {
+  pending: 0,
+  announced: 1,
+  accepted: 2,
+  rejected: 3,
+  "rejected-by-chairman": 4,
+};
+
+export const announcementTypeMapping = {
+  0: "rejected-procedure-proposal",
+  1: "accepted-procedure-proposal",
+  2: "suggested-procedure-proposal",
+  3: "voting",
+  4: "announcement",
+  5: "user-ban",
+};
+
+export const announcementTypeMappingRev = {
+  "rejected-procedure-proposal": 0,
+  "accepted-procedure-proposal": 1,
+  "suggested-procedure-proposal": 2,
+  voting: 3,
+  announcement: 4,
+  "user-ban": 5,
+};
+
 /**
  * Parse single post from the API.
  *
@@ -94,8 +130,25 @@ export const parseRawPost = (rawPost) => {
   };
 
   if (post.type === "procedure-proposal") {
-    post.state = postsStateMapping[post.state];
+    post.state = postsStateMapping[rawPost.state];
   }
 
   return post;
 };
+
+/**
+ * Parse single announcement from the API.
+ *
+ * @param {any} rawAnnouncement
+ * @returns {CF2021.Announcement}
+ */
+export const parseRawAnnouncement = (rawAnnouncement) => {
+  const announcement = {
+    ...pick(rawAnnouncement, ["id", "content", "link"]),
+    datetime: new Date(rawAnnouncement.datetime),
+    type: announcementTypeMapping[rawAnnouncement.type],
+    seen: false,
+  };
+
+  return announcement;
+};
diff --git a/src/ws/connection.js b/src/ws/connection.js
index ab2a5f1d863799b3e79663e0b49e4f2d111157d7..df922b2d76c43d52aa8720e199e9b32cf9328388 100644
--- a/src/ws/connection.js
+++ b/src/ws/connection.js
@@ -1,10 +1,6 @@
 import WaitQueue from "wait-queue";
 
-import { handleRanking } from "./handlers";
-
-const handlerMap = {
-  ranked: handleRanking,
-};
+import { handlers } from "./handlers";
 
 function Worker() {
   const queue = new WaitQueue();
@@ -25,7 +21,7 @@ function Worker() {
         return console.error("[ws][worker] Missing `event` field");
       }
 
-      const handlerFn = handlerMap[data.event];
+      const handlerFn = handlers[data.event];
 
       if (!handlerFn) {
         return console.warn(`[ws][worker] Can't handle event '${data.event}'`);
@@ -33,7 +29,7 @@ function Worker() {
 
       handlerFn(data.payload || {});
     } catch (err) {
-      console.error("[ws][worker] Could not parse message as JSON.");
+      console.error("[ws][worker] Could not parse message.", err);
     }
   };
 
@@ -80,7 +76,7 @@ export const connect = ({ onConnect }) => {
       );
 
       clearInterval(keepAliveInterval);
-      setTimeout(connect, 1000);
+      setTimeout(() => connect({ onConnect }), 1000);
     };
 
     ws.onerror = (err) => {
diff --git a/src/ws/handlers/announcements.js b/src/ws/handlers/announcements.js
new file mode 100644
index 0000000000000000000000000000000000000000..b49673f88b0656afcff79f1bae3c75d77d7efc4c
--- /dev/null
+++ b/src/ws/handlers/announcements.js
@@ -0,0 +1,28 @@
+import has from "lodash/has";
+
+import { AnnouncementStore } from "stores";
+import { parseRawAnnouncement, syncAnnoucementItemIds } from "utils";
+
+export const handleAnnouncementChanged = (payload) => {
+  AnnouncementStore.update((state) => {
+    if (state.items[payload.id]) {
+      if (has(payload, "content")) {
+        state.items[payload.id].content = payload.content;
+      }
+    }
+  });
+};
+
+export const handleAnnouncementCreated = (payload) => {
+  AnnouncementStore.update((state) => {
+    state.items[payload.id] = parseRawAnnouncement(payload);
+    syncAnnoucementItemIds(state);
+  });
+};
+
+export const handleAnnouncementDeleted = (payload) => {
+  AnnouncementStore.update((state) => {
+    delete state.items[payload.id];
+    syncAnnoucementItemIds(state);
+  });
+};
diff --git a/src/ws/handlers/index.js b/src/ws/handlers/index.js
index 4f89127aa97d187ec2f60a7c9aff2a1d7b80606e..b10730e64060537264959ca6a0f6950d79ec896f 100644
--- a/src/ws/handlers/index.js
+++ b/src/ws/handlers/index.js
@@ -1 +1,21 @@
-export * from "./posts";
+import {
+  handleAnnouncementChanged,
+  handleAnnouncementCreated,
+  handleAnnouncementDeleted,
+} from "./announcements";
+import {
+  handlePostChanged,
+  handlePostCreated,
+  handlePostRanked,
+} from "./posts";
+import { handleProgramEntryChanged } from "./program";
+
+export const handlers = {
+  announcement_changed: handleAnnouncementChanged,
+  announcement_created: handleAnnouncementCreated,
+  announcement_deleted: handleAnnouncementDeleted,
+  post_ranked: handlePostRanked,
+  post_changed: handlePostChanged,
+  post_created: handlePostCreated,
+  program_entry_changed: handleProgramEntryChanged,
+};
diff --git a/src/ws/handlers/posts.js b/src/ws/handlers/posts.js
index 7ef6c3fc5a16d873e5d0e17bb7f10216313f4f4d..a277ed8a0843072a0407132aa444614dbf49df97 100644
--- a/src/ws/handlers/posts.js
+++ b/src/ws/handlers/posts.js
@@ -1,7 +1,9 @@
+import has from "lodash/has";
+
 import { PostStore } from "stores";
-import { parseRawPost, updateWindowPosts } from "utils";
+import { parseRawPost, postsStateMapping, updateWindowPosts } from "utils";
 
-export const handleRanking = (payload) => {
+export const handlePostRanked = (payload) => {
   PostStore.update((state) => {
     if (state.items[payload.id]) {
       state.items[payload.id].ranking.likes = payload["ranking_likes"];
@@ -17,20 +19,25 @@ export const handleRanking = (payload) => {
   });
 };
 
-export const handleChanged = (payload) => {
+export const handlePostChanged = (payload) => {
   PostStore.update((state) => {
     if (state.items[payload.id]) {
-      state.items[payload.id].content = payload.content;
-      state.items[payload.id].modified = true;
+      if (has(payload, "content")) {
+        state.items[payload.id].content = payload.content;
+        state.items[payload.id].modified = true;
+      }
+
+      if (has(payload, "state")) {
+        state.items[payload.id].state = postsStateMapping[payload.state];
+      }
     }
   });
 };
 
-export const handleCreated = (payload) => {
+export const handlePostCreated = (payload) => {
   PostStore.update((state) => {
-    if (state.items[payload.id]) {
-      state.items[payload.id] = parseRawPost(payload);
-      updateWindowPosts(state);
-    }
+    state.items[payload.id] = parseRawPost(payload);
+    state.itemCount = Object.keys(state.items).length;
+    updateWindowPosts(state);
   });
 };
diff --git a/src/ws/handlers/program.js b/src/ws/handlers/program.js
new file mode 100644
index 0000000000000000000000000000000000000000..c677db120f93aa7e31e7c307bca8fe852dc0d76f
--- /dev/null
+++ b/src/ws/handlers/program.js
@@ -0,0 +1,16 @@
+import has from "lodash/has";
+
+import { ProgramStore } from "stores";
+
+export const handleProgramEntryChanged = (payload) => {
+  ProgramStore.update((state) => {
+    if (state.items[payload.id]) {
+      if (has(payload, "discussion_opened")) {
+        state.items[payload.id].discussionOpened = payload.discussion_opened;
+      }
+      if (has(payload, "title")) {
+        state.items[payload.id].title = payload.title;
+      }
+    }
+  });
+};
diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts
index 048e486fc291baaa63409551f185a83952b49a85..63f15a564d6993b5dc6d2502a610016b285d4d7f 100644
--- a/typings/cf2021.d.ts
+++ b/typings/cf2021.d.ts
@@ -1,134 +1,140 @@
 declare namespace CF2021 {
-    interface ProgramScheduleEntry {
-        id: number;
-        number: string;
-        title: string;
-        proposer: string;
-        description?: string;
-        expectedStartAt: Date;
-        expectedFinishAt?: Date;
-    }
-
-    export interface ProgramStorePayload {
-        current?: ProgramScheduleEntry & {
-            discussionOpened: boolean;
-        }
-        schedule: ProgramScheduleEntry[];
-    }
-
-    interface GroupMapping {
-        id: number;
-        code: string;
-        name: string;
-    }
-
-    export interface AnonymousAuthStorePayload {
-        isAuthenticated: false;
-        groupMappings: GroupMapping[];
-    }
-
-    export interface UserAuthStorePayload extends AnonymousAuthStorePayload {
-        isAuthenticated: true;
-        user: {
-            name: string;
-            username: string;
-            groups: string[];
-            accessToken: string;
-        };
-    }
-
-    export type AuthStorePayload =
-        | AnonymousAuthStorePayload
-        | UserAuthStorePayload;
-
-    export type AnnouncementType =
-        | "rejected-procedure-proposal"
-        | "accepted-procedure-proposal"
-        | "suggested-procedure-proposal"
-        | "voting"
-        | "announcement"
-        | "user-ban";
-
-    export interface Announcement {
-        id: string;
-        datetime: Date;
-        type: AnnouncementType;
-        content: string;
-        link?: string;
-        relatedPostId: string;
-        seen: boolean;
-    }
-
-    export interface AnnouncementStorePayload {
-        items: Announcement[];
-    }
-
-    export type PostType = "post" | "procedure-proposal";
-
-    export interface AbstractPost {
-        id: number;
-        datetime: Date;
-        author: {
-            id: number;
-            name: string;
-            username: string;
-            group: string;
-        };
-        type: PostType;
-        content: string;
-        ranking: {
-            score: number;
-            likes: number;
-            dislikes: number;
-            myVote: "like" | "dislike" | "none";
-        };
-        historyLog: {
-          attribute: string;
-          newValue: string;
-          datetime: Date;
-          originator: "self" | "chairman";
-        }[];
-        modified: boolean;
-        archived: boolean;
-        hidden: boolean;
-        seen: boolean;
-    }
-
-    export interface DiscussionPost extends AbstractPost {
-        type: "post";
-    }
-
-    export interface ProposalPost extends AbstractPost {
-        type: "procedure-proposal";
-        state:
-            | "pending"
-            | "announced"
-            | "accepted"
-            | "rejected"
-            | "rejected-by-chairman";
-    }
-
-    export type Post = ProposalPost | DiscussionPost;
-
-    export interface PostStoreItems {
-      [key: string]: Post;
-    }
-
-    export interface PostStoreFilters {
-      flags: "all" | "active" | "archived";
-        sort: "byDate" | "byScore";
-        type: "all" | "proposalsOnly" | "discussionOnly";
-    }
-
-    export interface PostStorePayload {
-        items: PostStoreItems;
-        itemCount: number;
-        window: {
-          items: string[];
-          itemCount: number;
-          page: number;
-          perPage: number;
-        };
-        filters: PostStoreFilters;
-    }
+  interface ProgramScheduleEntry {
+    id: number;
+    number: string;
+    title: string;
+    proposer: string;
+    discussionOpened: boolean;
+    description?: string;
+    expectedStartAt: Date;
+    expectedFinishAt?: Date;
+  }
+
+  export interface ProgramStorePayload {
+    items: {
+      [key: number]: ProgramScheduleEntry;
+    };
+    currentId?: number;
+    scheduleIds: number[];
+  }
+
+  interface GroupMapping {
+    id: number;
+    code: string;
+    name: string;
+  }
+
+  export interface AnonymousAuthStorePayload {
+    isAuthenticated: false;
+  }
+
+  export interface UserAuthStorePayload extends AnonymousAuthStorePayload {
+    isAuthenticated: true;
+    user: {
+      name: string;
+      username: string;
+      role: "regp" | "member" | "chairman";
+      accessToken: string;
+    };
+  }
+
+  export type AuthStorePayload =
+    | AnonymousAuthStorePayload
+    | UserAuthStorePayload;
+
+  export type AnnouncementType =
+    | "rejected-procedure-proposal"
+    | "accepted-procedure-proposal"
+    | "suggested-procedure-proposal"
+    | "voting"
+    | "announcement"
+    | "user-ban";
+
+  export interface Announcement {
+    id: number;
+    datetime: Date;
+    type: AnnouncementType;
+    content: string;
+    link?: string;
+    relatedPostId: string;
+    seen: boolean;
+  }
+
+  export interface AnnouncementStorePayload {
+    items: {
+      [key: number]: Announcement;
+    };
+    itemIds: number[];
+  }
+
+  export type PostType = "post" | "procedure-proposal";
+
+  export interface AbstractPost {
+    id: number;
+    datetime: Date;
+    author: {
+      id: number;
+      name: string;
+      username: string;
+      group: string;
+    };
+    type: PostType;
+    content: string;
+    ranking: {
+      score: number;
+      likes: number;
+      dislikes: number;
+      myVote: "like" | "dislike" | "none";
+    };
+    historyLog: {
+      attribute: string;
+      newValue: string;
+      datetime: Date;
+      originator: "self" | "chairman";
+    }[];
+    modified: boolean;
+    archived: boolean;
+    hidden: boolean;
+    seen: boolean;
+  }
+
+  export interface DiscussionPost extends AbstractPost {
+    type: "post";
+  }
+
+  export type ProposalPostState =
+    | "pending"
+    | "announced"
+    | "accepted"
+    | "rejected"
+    | "rejected-by-chairman";
+
+  export interface ProposalPost extends AbstractPost {
+    type: "procedure-proposal";
+    state: ProposalPostState;
+  }
+
+  export type Post = ProposalPost | DiscussionPost;
+
+  export interface PostStoreItems {
+    [key: string]: Post;
+  }
+
+  export interface PostStoreFilters {
+    flags: "all" | "active" | "archived";
+    sort: "byDate" | "byScore";
+    type: "all" | "proposalsOnly" | "discussionOnly";
+  }
+
+  export interface PostStorePayload {
+    items: PostStoreItems;
+    itemCount: number;
+    window: {
+      items: string[];
+      itemCount: number;
+      page: number;
+      perPage: number;
+    };
+    filters: PostStoreFilters;
+  }
 }