Skip to content
Draft
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 docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,27 @@ services:
retries: 3
start_period: 10s

# ============================================================================
# calendar_service (Port 8101)
# Calendar events + attendees - MacroDB
# ============================================================================
calendar_service:
<<: [*common-env, *rust-services-image]
command: ["/app/out/calendar_service"]
ports:
- "8101:8080"
networks:
databases:
services:
aliases:
- calendar-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
Comment on lines +104 to +119

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add Postgres startup dependency for local reliability.

calendar_service is DB-backed but has no depends_on, so it can start before Postgres and fail during initial connect. Add a Postgres dependency to match existing service startup patterns.

Suggested fix
   calendar_service:
     <<: [*common-env, *rust-services-image]
     command: ["/app/out/calendar_service"]
     ports:
       - "8101:8080"
+    depends_on:
+      - postgres
     networks:
       databases:
       services:
         aliases:
           - calendar-service
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
calendar_service:
<<: [*common-env, *rust-services-image]
command: ["/app/out/calendar_service"]
ports:
- "8101:8080"
networks:
databases:
services:
aliases:
- calendar-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
calendar_service:
<<: [*common-env, *rust-services-image]
command: ["/app/out/calendar_service"]
ports:
- "8101:8080"
depends_on:
- postgres
networks:
databases:
services:
aliases:
- calendar-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 104 - 119, The calendar_service container
lacks a startup dependency on Postgres so it may attempt DB connections before
the DB is ready; update the calendar_service service block to add a depends_on
entry referencing the Postgres service (e.g., depends_on: - postgres or the
project's DB service name) and include the appropriate condition for
service_healthy if you use healthchecks (e.g., condition: service_healthy) to
ensure calendar_service (command /app/out/calendar_service) waits for the DB
before starting.


# ============================================================================
# document_cognition_service (Port 8085)
# Document analysis and processing
Expand Down
12 changes: 12 additions & 0 deletions js/app/packages/app/component/app-sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { isTouchDevice } from '@core/mobile/isTouchDevice';
import LogoIcon from '@icon/macro-logo.svg';
import { AnimatedSquareCommandKIcon } from '@icon/square-command-k';
import { AnimatedSquareSidebarIcon } from '@icon/square-sidebar';
import { AnimatedCalendarIcon } from '@icon/wide-calendar';
import { AnimatedCallIcon } from '@icon/wide-call';
import { AnimatedChannelIcon } from '@icon/wide-channel';
import { AnimatedEmailIcon } from '@icon/wide-email';
Expand Down Expand Up @@ -773,6 +774,15 @@ const DASHBOARD_LINK: SidebarItem = {
hotkeyToken: TOKENS.sidebar.goTo.home,
};

const CALENDAR_LINK: SidebarItem = {
id: 'calendar',
label: 'Calendar',
href: '/calendar',
icon: AnimatedCalendarIcon,
hotkey: 'd',
hotkeyToken: TOKENS.sidebar.goTo.calendar,
};

export const AppSidebar = (props: AppSidebarProps) => {
const analytics = useAnalytics();
const layout = useSplitLayout();
Expand Down Expand Up @@ -823,6 +833,8 @@ export const AppSidebar = (props: AppSidebarProps) => {
links = [...links.slice(0, idx + 1), CALLS_LINK, ...links.slice(idx + 1)];
}

links = [...links, CALENDAR_LINK];

return links;
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Home } from '@app/component/home';
import type { SetPredicatesInput } from '@app/component/next-soup/filters/filter-store/predicates-store';
import type { Query } from '@app/component/next-soup/filters/filter-store/types';
import { SoupView } from '@app/component/next-soup/soup-view/soup-view';
import { Calendar } from '@block-calendar';
import { ChannelCompose } from '@block-channel/component/Compose';
import { ComposeTask } from '@block-md/component/ComposeTask';
import { useIsAuthenticated } from '@core/auth';
Expand Down Expand Up @@ -280,6 +281,13 @@ registerComponent(
);
})
);
registerComponent(
'calendar',
withAuth(() => {
usePageViewTracking('calendar');
return <Calendar />;
})
);
/** END - APP ROUTES */

registerComponent('loading', () => <LoadingBlock />);
Expand Down
44 changes: 44 additions & 0 deletions js/app/packages/block-calendar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# block-calendar

A Google-Calendar-style calendar surface, opened from the sidebar ("Calendar").

## Views

- **Week** (`w`) — 7-day time grid (default)
- **Day** (`d`) — single-day time grid
- **List** (`l`) — agenda of upcoming events grouped by day

## Keyboard shortcuts

Active while the calendar is focused:

| Key | Action |
| --- | ----------------- |
| `j` | Next screen |
| `k` | Previous screen |
| `t` | Jump to today |
| `n` | New event |
| `w` | Week view |
| `d` | Day view |
| `l` | List view |

`g d` (go-to leader) opens the calendar from anywhere.

## Events & invites

Events are persisted by the `calendar_service` backend
(`rust/cloud-storage/calendar`). Data access lives in `@queries/calendar`; the
wire client is `@service-calendar`.

Click an empty slot to create an event, or an event to edit it. Add guests by
email; **Save & send invites** records the invite on the backend and emails
attendees from the user's connected mailbox (via the email service), including
an `.ics` payload. The dialog can also download a standalone `.ics`.

## Layers

- `model/` — frontend domain types (instant-based, decoupled from the wire DTOs)
- `util/` — date math, iCalendar generation, invite-email composition
- `component/` — `Calendar` (orchestrator + hotkeys), `Toolbar`, `TimeGrid`
(week/day), `ListView`, `EventDialog`, and the `CalendarContext` that wires
state to queries/mutations.
99 changes: 99 additions & 0 deletions js/app/packages/block-calendar/component/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
createHotkeyGroup,
registerHotkey,
useHotkeyDOMScope,
} from '@core/hotkey/hotkeys';
import { type HotkeyToken, TOKENS } from '@core/hotkey/tokens';
import type { ValidHotkey } from '@core/hotkey/types';
import { Match, onCleanup, onMount, Switch } from 'solid-js';
import { daysForView } from '../util/dates';
import { CalendarProvider, useCalendar } from './CalendarContext';
import { EventDialog } from './EventDialog';
import { ListView } from './ListView';
import { TimeGrid } from './TimeGrid';
import { Toolbar } from './Toolbar';

function CalendarInner() {
const calendar = useCalendar();
const [attachHotkeys, scopeId] = useHotkeyDOMScope('calendar');
const group = createHotkeyGroup();
let rootRef: HTMLDivElement | undefined;

onMount(() => {
if (rootRef) {
attachHotkeys(rootRef);
// Focus the root so j/k work without an explicit click first.
rootRef.focus();
}

const bind = (
hotkey: ValidHotkey,
hotkeyToken: HotkeyToken,
description: string,
run: () => void
) =>
group.add(
registerHotkey({
hotkey,
scopeId,
hotkeyToken,
description,
keyDownHandler: () => {
run();
return true;
},
})
);

// Primary navigation: vim-style j/k step one screen forward/back, matching
// the soup list convention (j = next, k = previous).
bind('j', TOKENS.calendar.next, 'Next', calendar.goNext);
bind('k', TOKENS.calendar.prev, 'Previous', calendar.goPrev);
bind('t', TOKENS.calendar.today, 'Today', calendar.goToday);
bind('n', TOKENS.calendar.newEvent, 'New event', () => calendar.openNew());
bind('d', TOKENS.calendar.viewDay, 'Day view', () =>
calendar.setView('day')
);
bind('w', TOKENS.calendar.viewWeek, 'Week view', () =>
calendar.setView('week')
);
bind('l', TOKENS.calendar.viewList, 'List view', () =>
calendar.setView('list')
);
});

onCleanup(() => group.dispose());

return (
<div
ref={rootRef}
tabindex={0}
class="flex h-full flex-col bg-surface outline-none select-none"
>
<Toolbar />
<div class="min-h-0 flex-1">
<Switch>
<Match when={calendar.view() === 'week'}>
<TimeGrid days={daysForView('week', calendar.anchor())} />
</Match>
<Match when={calendar.view() === 'day'}>
<TimeGrid days={daysForView('day', calendar.anchor())} />
</Match>
<Match when={calendar.view() === 'list'}>
<ListView />
</Match>
</Switch>
</div>
<EventDialog />
</div>
);
}

/** The calendar surface, opened from the sidebar. */
export default function Calendar() {
return (
<CalendarProvider>
<CalendarInner />
</CalendarProvider>
);
}
Loading
Loading