GitFOSS
feat(layout): redesign repo/pulls
+ 1028
- 512
this is pre-requesite to make it use system scheme/colors (Material You
like)

@@ -10,6 +10,7 @@ gitfoss.dev {
   }
 }
 
-www.gitfoss.dev, gitfoss.sk, gitfoss.tech {
+// www.gitfoss.dev, gitfoss.sk, gitfoss.tech {
+www.gitfoss.dev {
   redir https://gitfoss.dev{uri} 301
-}
+}

@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1778263212194,
+  "_generatedAtUnix": 1778493305245,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "assets": {

...
@@ -16,25 +16,25 @@
   },
   "islands": {
     "AppRouter": {
-      "hash": "8afbbf1045fc9513fc75dd93d92cb6b7fee5823e",
+      "hash": "028bbfbd3942c9040316a7b8e11f791de36c8b62",
       "pathSource": "./app/islands/AppRouter.tsx",
       "pathBundle": "./public/.islands/AppRouter.bundle.js",
       "pathSourceMap": "./public/.islands/AppRouter.bundle.js.map"
     },
     "Code": {
-      "hash": "2f1068f28f37b4c9fc39d082e5e443a49268bf80",
+      "hash": "4243c08b20c3b66f9613648381e0601f0003c836",
       "pathSource": "./app/islands/Code.tsx",
       "pathBundle": "./public/.islands/Code.bundle.js",
       "pathSourceMap": "./public/.islands/Code.bundle.js.map"
     },
     "InstantRouterIndicator": {
-      "hash": "882b1079900ca62bbed9edb917e9f98d25510389",
+      "hash": "4ba3cf445852b5aceb94ca68277e955b3bbd9e58",
       "pathSource": "./app/islands/InstantRouterIndicator.tsx",
       "pathBundle": "./public/.islands/InstantRouterIndicator.bundle.js",
       "pathSourceMap": "./public/.islands/InstantRouterIndicator.bundle.js.map"
     },
     "PullRequestSourceSelect": {
-      "hash": "8e551193aa6fb6db829580555a4fd079c7f9f3d2",
+      "hash": "7a36e28e0dd310c98392a6de451b36cb352e017f",
       "pathSource": "./app/islands/PullRequestSourceSelect.tsx",
       "pathBundle": "./public/.islands/PullRequestSourceSelect.bundle.js",
       "pathSourceMap": "./public/.islands/PullRequestSourceSelect.bundle.js.map"

...
@@ -46,13 +46,13 @@
       "pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"
     },
     "RepositoryCommitSummaryLine": {
-      "hash": "7c3d65ecbffa640a17a60f90c9817fa3ebaa72ee",
+      "hash": "ab3729f98c645091f68deafdab42bec0ffc775c5",
       "pathSource": "./app/islands/RepositoryCommitSummaryLine.tsx",
       "pathBundle": "./public/.islands/RepositoryCommitSummaryLine.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryCommitSummaryLine.bundle.js.map"
     },
     "RepositoryCreateForm": {
-      "hash": "546ac2b54b8b42e6dd1b499cdd865b650659900e",
+      "hash": "f5576f3b97071bb864350953ee55c532c11d9553",
       "pathSource": "./app/islands/RepositoryCreateForm.tsx",
       "pathBundle": "./public/.islands/RepositoryCreateForm.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryCreateForm.bundle.js.map"

...
@@ -64,13 +64,13 @@
       "pathSourceMap": "./public/.islands/RepositoryFilesDiffsList.bundle.js.map"
     },
     "RepositoryForkForm": {
-      "hash": "e744c88ef2c2885a32b5594732b1cfab088f4350",
+      "hash": "fd08d2bea0816d5fbea2080e629631e1e1e16bd0",
       "pathSource": "./app/islands/RepositoryForkForm.tsx",
       "pathBundle": "./public/.islands/RepositoryForkForm.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryForkForm.bundle.js.map"
     },
     "RepositoryHero": {
-      "hash": "86d474ab9b3342158f964569b85d3e6b9aeba9e3",
+      "hash": "b0ab4c086a536112a3d6bc3ff3c6c65d29d25288",
       "pathSource": "./app/islands/RepositoryHero.tsx",
       "pathBundle": "./public/.islands/RepositoryHero.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryHero.bundle.js.map"

...
@@ -82,19 +82,19 @@
       "pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
     },
     "RepositoryPullRequestCreateForm": {
-      "hash": "6b7fca0b6cabeacdb1cc6129535dfdb67ab0e9e0",
+      "hash": "e8c07459002da048557b8eaedbae5656f70e424f",
       "pathSource": "./app/islands/RepositoryPullRequestCreateForm.tsx",
       "pathBundle": "./public/.islands/RepositoryPullRequestCreateForm.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryPullRequestCreateForm.bundle.js.map"
     },
     "RepositoryTreeView": {
-      "hash": "dc566c91c41bd9fc04da9e7017cd168f008c8b72",
+      "hash": "6774ee402778e12236e8501a4a49339f68b34edb",
       "pathSource": "./app/islands/RepositoryTreeView.tsx",
       "pathBundle": "./public/.islands/RepositoryTreeView.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
     },
     "SSHKeyItem": {
-      "hash": "31e551858d85e502e5de1a37699de2225916ba96",
+      "hash": "ee31649cf2c982c83452e871d1e61836fef95ae5",
       "pathSource": "./app/islands/SSHKeyItem.tsx",
       "pathBundle": "./public/.islands/SSHKeyItem.bundle.js",
       "pathSourceMap": "./public/.islands/SSHKeyItem.bundle.js.map"

...
@@ -106,7 +106,7 @@
       "pathSource": "./app/views/HomeView.tsx"
     },
     "InternalErrorView": {
-      "hash": "af1d1eb51c24336b7f57985d6a8039aebccd7387",
+      "hash": "aae72693c596a7f8ff05092541bb95d0ef91365a",
       "pathSource": "./app/views/InternalErrorView.tsx"
     },
     "LoginView": {

...
@@ -122,23 +122,23 @@
       "pathSource": "./app/views/organization/OrganizationDetailsView.tsx"
     },
     "RepositoryBrowserView": {
-      "hash": "7b41bf07b8f2b7533023c0ddd01a254ccc04c216",
+      "hash": "81e6af41d08eeaa67205562808e6242aa9f80190",
       "pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
     },
     "RepositoryCommitsLogView": {
-      "hash": "0d8c3d27a27e82f345ef5be98122927103a60d09",
+      "hash": "ff424e3c6b651f9ec41cade96ade44e5911961c5",
       "pathSource": "./app/views/repository/RepositoryCommitsLogView.tsx"
     },
     "RepositoryCompareView": {
-      "hash": "a7ac4991abcd466ff0d6862ab3cc1f3c1541f317",
+      "hash": "6a9dfc6918a092e2d5477c654da192ccc4923b11",
       "pathSource": "./app/views/repository/RepositoryCompareView.tsx"
     },
     "RepositoryCreateView": {
-      "hash": "d38e91def64064530cad123022ea856100bf79ae",
+      "hash": "9f0f39d67a7f8d4d5acc229eca4328e427e62747",
       "pathSource": "./app/views/repository/RepositoryCreateView.tsx"
     },
     "RepositoryDetailsView": {
-      "hash": "6162522f8245f1287a3b0a974278f38308203d20",
+      "hash": "1a1dae11fde756abeac809db2f7fb7417de1620f",
       "pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
     },
     "RepositoryExploreView": {

...
@@ -146,23 +146,23 @@
       "pathSource": "./app/views/repository/RepositoryExploreView.tsx"
     },
     "RepositoryForkView": {
-      "hash": "af5b2ddc25d33b4d48f0ac2dfc6e4e6537be7354",
+      "hash": "b8067358faf3af2ff271b14d1c710ea3b146e333",
       "pathSource": "./app/views/repository/RepositoryForkView.tsx"
     },
     "RepositoryShowObjectView": {
-      "hash": "428390b5d94c5806b809cde4bf912a663be0ca36",
+      "hash": "17463b3826771e745e6bf2234edace3482d41c68",
       "pathSource": "./app/views/repository/RepositoryShowObjectView.tsx"
     },
     "RepositoryPullRequestCreateView": {
-      "hash": "25a239fcc8d0aa05e3cc312d51b2e6f018e7cd65",
+      "hash": "0120b3883fd89467285f44cb02a11fdebd314363",
       "pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestCreateView.tsx"
     },
     "RepositoryPullRequestDetailsView": {
-      "hash": "2fa65a3012cc05738f83656d4f0e773ebd0e5bb3",
+      "hash": "8d09c65ae26d7865c472300bf5a102470516b0f2",
       "pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestDetailsView.tsx"
     },
     "RepositoryPullRequestsView": {
-      "hash": "1816a6fb6b830daa31d3a371ef2712b3cd634188",
+      "hash": "3a3141f2902e33a4fe531d86153a14947c727dfd",
       "pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestsView.tsx"
     },
     "SettingsKeyAddView": {

...
@@ -178,7 +178,7 @@
       "pathSource": "./app/views/settings/SettingsKeysListView.tsx"
     },
     "SettingsView": {
-      "hash": "0d7e4617b9b905ab8cce136062e9e3504d9fa138",
+      "hash": "ca5ade3fa3a5c34faaa321fbdb5bc844f46ce37a",
       "pathSource": "./app/views/settings/SettingsView.tsx"
     },
     "UserDashboardView": {

app/components/Card.styled.ts
@@ -16,5 +16,5 @@ export const Card = styled.div<WithThemeSchemeProp>`
     border: 1px solid ${NamedColors.BORDER_CARD[themeScheme]};
   `};
 
-  border-radius: 8px;
+  border-radius: 12px;
 `;

new file
app/components/DrawerPrimary.tsx
@@ -0,0 +1,297 @@
+// 3rd-party
+import React from "react";
+import styled, { css } from "styled-components";
+// import Color from "color";
+
+// app
+import { Const } from "../const";
+import { Chip } from "./Chip";
+import { NamedColors } from "../utils/style";
+import { type CommonViewProps, type WithThemeSchemeProp } from "../types";
+import { buildRouteLink } from "../utils/shared";
+import { AppRoute } from "../routes.defs";
+
+export const DrawerPrimary = ({
+  visible = false,
+  commonProps,
+  themeScheme,
+  orgSlug,
+  repoSlug,
+  currentRef = Const.DEFAULT_HEAD_REF,
+  path = "/",
+}: WithThemeSchemeProp & {
+  visible: boolean;
+  commonProps: CommonViewProps;
+  orgSlug: string;
+  repoSlug: string;
+  currentRef?: string;
+  path?: string;
+}) => {
+  const pathRepo = buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
+    orgSlug: orgSlug,
+    repoSlug: repoSlug,
+  });
+
+  const pathRepoTrailing = buildRouteLink(
+    AppRoute.REPOSITORY_DETAILS_WITH_TRAILING_SLASH,
+    {
+      orgSlug: orgSlug,
+      repoSlug: repoSlug,
+    },
+  );
+
+  const pathFiles = buildRouteLink(AppRoute.REPOSITORY_BROWSER, {
+    orgSlug: orgSlug,
+    repoSlug: repoSlug,
+    currentRef: currentRef,
+    "*": path,
+  });
+
+  const pathPulls = buildRouteLink(AppRoute.REPOSITORY_PULL_REQUESTS, {
+    orgSlug: orgSlug,
+    repoSlug: repoSlug,
+  });
+
+  if (visible === false) {
+    return null;
+  }
+
+  return (
+    <StyledDrawerPrimary themeScheme={themeScheme}>
+      <StyledDrawerHeader>
+        <StyledLogoArea themeScheme={themeScheme}>
+          <a href={"/"}>
+            <h1>{Const.APP_NAME}</h1>
+          </a>
+        </StyledLogoArea>
+      </StyledDrawerHeader>
+      <StyledDrawerContent>
+        <StyledDrawerListHeader>
+          <span>acme-org</span>
+          <span>/</span>
+          <span>my-app</span>
+        </StyledDrawerListHeader>
+        <StyledDrawerList>
+          <StyledDrawerListItem
+            themeScheme={themeScheme}
+            href={pathFiles}
+            className={
+              [pathFiles, pathRepo, pathRepoTrailing].includes(
+                commonProps.path || "/",
+              )
+                ? "active"
+                : undefined
+            }
+          >
+            <span>Files</span>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem
+            themeScheme={themeScheme}
+            href={pathPulls}
+            className={commonProps.path === pathPulls ? "active" : undefined}
+          >
+            <span>Pull Requests</span>
+            <Chip>0</Chip>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>Tests & Coverage</span>
+            <Chip>0</Chip>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>Builds</span>
+            <Chip>0</Chip>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>Issues</span>
+            <Chip>0</Chip>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>API Reference</span>
+            <Chip>0</Chip>
+          </StyledDrawerListItem>
+        </StyledDrawerList>
+        <StyledDrawerListHeader></StyledDrawerListHeader>
+        <StyledDrawerList></StyledDrawerList>
+      </StyledDrawerContent>
+      <StyledDrawerFooter>
+        <StyledDrawerList>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>Feedback</span>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>Help Center</span>
+          </StyledDrawerListItem>
+          <StyledDrawerListItem themeScheme={themeScheme} disabled>
+            <span>Settings</span>
+          </StyledDrawerListItem>
+        </StyledDrawerList>
+      </StyledDrawerFooter>
+    </StyledDrawerPrimary>
+  );
+};
+
+const StyledDrawerPrimary = styled.aside<
+  WithThemeSchemeProp & { color?: string }
+>`
+  ${({ themeScheme }) => css`
+    min-width: 230px;
+    max-width: 320px;
+    height: 100vh;
+
+    display: flex;
+    flex-flow: column nowrap;
+    align-items: center;
+    justify-content: center;
+
+    position: sticky;
+    top: 0;
+    left: 0;
+    bottom: 0;
+
+    background: ${NamedColors.HEADER[themeScheme]};
+    border-right: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]};
+
+    @media only screen and (max-width: 768px) {
+      display: none;
+    }
+  `};
+`;
+
+const StyledDrawerHeader = styled.header`
+  width: 100%;
+  height: 64px;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+`;
+
+const StyledLogoArea = styled.div<WithThemeSchemeProp>`
+  @media only screen and (max-width: 768px) {
+    & > a > h1 {
+      font-size: 22px;
+    }
+  }
+
+  & > a {
+    display: flex;
+    flex-flow: row nowrap;
+    justify-content: flex-start;
+    align-items: center;
+
+    ${({ themeScheme }) => css`
+      color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
+    `};
+
+    h1 {
+      margin: 0;
+    }
+  }
+`;
+
+const StyledDrawerContent = styled.main`
+  width: 100%;
+  max-width: 100%;
+  min-width: 100%;
+  height: 100%;
+
+  flex: 1;
+
+  padding: 12px;
+`;
+
+const StyledDrawerListHeader = styled.section`
+  width: 100%;
+  height: 40px;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  font-weight: bold;
+
+  margin-bottom: 4px;
+`;
+
+const StyledDrawerList = styled.section`
+  width: 100%;
+
+  display: flex;
+  flex-flow: column nowrap;
+  justify-content: flex-start;
+  align-items: center;
+
+  gap: 2px;
+`;
+
+const StyledDrawerListItem = styled.a<
+  WithThemeSchemeProp & { disabled?: boolean }
+>`
+  ${({ disabled, themeScheme }) => css`
+    width: 100%;
+    height: 40px;
+
+    display: flex;
+    flex-flow: row nowrap;
+    justify-content: flex-start;
+    align-items: center;
+
+    padding: 0 12px;
+
+    font-weight: normal;
+    font-size: 14px;
+    color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
+    border-radius: 20px;
+    text-decoration: none;
+
+    & > span:nth-child(1) {
+      flex: 1;
+    }
+
+    & > ${Chip} {
+      color: ${NamedColors.TEXT_MUTED[themeScheme]};
+      background-color: ${NamedColors.CARD_OVERLAY[themeScheme]};
+    }
+
+    ${(disabled == null || disabled === false) &&
+    css`
+      &.active,
+      &:not(:disabled):hover {
+        color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
+        background-color: ${NamedColors.CARD[themeScheme]};
+        font-weight: bold;
+        font-family: monospace;
+      }
+    `};
+
+    &:hover {
+      text-decoration: none;
+    }
+
+    &:disabled {
+      color: ${NamedColors.TEXT_MUTED[themeScheme]};
+    }
+
+    ${disabled &&
+    css`
+      color: ${NamedColors.TEXT_MUTED[themeScheme]};
+
+      & > ${Chip} {
+        color: ${NamedColors.TEXT_MUTED[themeScheme]};
+        opacity: 0.3;
+      }
+    `}
+  `}
+`;
+
+const StyledDrawerFooter = styled.footer`
+  width: 100%;
+  height: 128px;
+
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+
+  margin: 16px 0;
+  padding: 0 12px;
+`;

app/components/Layout.tsx
@@ -7,12 +7,18 @@ import { Const } from "../const";
 import { NamedColors } from "../utils/style";
 // app islands
 import InstantRouterIndicator from "../islands/InstantRouterIndicator";
-
+// app components
 import { PageHeader } from "./PageHeader";
+import { DrawerPrimary } from "./DrawerPrimary";
 
 interface LayoutProps extends CommonViewProps {
   foo?: boolean;
   appVersion: string;
+  showDrawerPrimary?: boolean;
+  orgSlug?: string;
+  repoSlug?: string;
+  currentRef?: string;
+  path?: string;
 }
 
 const BRANDLINE_HEIGHT = 4;

...
@@ -23,7 +29,17 @@ function removeCommentsAndSpacing(str = "") {
 }
 
 export const Layout: FC<LayoutProps & WithThemeSchemeProp> = (commonProps) => {
-  const { appVersion, children, gitStamp, themeScheme } = commonProps;
+  const {
+    appVersion,
+    children,
+    gitStamp,
+    themeScheme,
+    showDrawerPrimary = false,
+    orgSlug = "",
+    repoSlug = "",
+    currentRef = Const.DEFAULT_HEAD_REF,
+    path = "",
+  } = commonProps;
 
   const sharedProps = {
     themeScheme,

...
@@ -78,22 +94,38 @@ export const Layout: FC<LayoutProps & WithThemeSchemeProp> = (commonProps) => {
         <div data-islandid={`${InstantRouterIndicator.name}$$0`}>
           <InstantRouterIndicator />
         </div>
-        <StyledPageHeaderWrapper {...sharedProps}>
-          <PageHeader commonProps={commonProps} themeScheme={themeScheme} />
-        </StyledPageHeaderWrapper>
         <StyledPageWrapper>
-          <StyledChildrenWrapper {...sharedProps}>
+          <DrawerPrimary
+            commonProps={commonProps as any}
+            themeScheme={themeScheme}
+            visible={showDrawerPrimary}
+            orgSlug={orgSlug}
+            repoSlug={repoSlug}
+            currentRef={currentRef}
+            path={path}
+          />
+          <StyledChildrenWrapper
+            {...sharedProps}
+            showDrawerPrimary={showDrawerPrimary}
+          >
+            <StyledPageHeaderWrapper {...sharedProps}>
+              <PageHeader
+                commonProps={commonProps}
+                themeScheme={themeScheme}
+                forceShowLogo={showDrawerPrimary !== true}
+              />
+            </StyledPageHeaderWrapper>
             {children}
+            <StyledFooterWrapper>
+              <p>
+                <a href={"https://gitfoss.dev/ethicdevs/gitfoss"}>
+                  {Const.APP_NAME} &bull; v{appVersion} (#
+                  {gitStamp.slice(0, 7)}) &bull; MIT License
+                </a>
+              </p>
+            </StyledFooterWrapper>
           </StyledChildrenWrapper>
         </StyledPageWrapper>
-        <StyledFooterWrapper>
-          <p>
-            <a href={"https://gitfoss.io/ethicdevs/gitfoss"}>
-              {Const.APP_NAME} - v{appVersion} (#{gitStamp.slice(0, 7)}) - MIT
-              License
-            </a>
-          </p>
-        </StyledFooterWrapper>
       </StyledLayoutWrapper>
     </>
   );

...
@@ -169,7 +201,7 @@ const StyledPageWrapper = styled.div`
   min-height: calc(100% - ${BRANDLINE_HEIGHT + HEADER_HEIGHT}px);
 `;
 
-const StyledChildrenWrapper = styled.div`
+const StyledChildrenWrapper = styled.div<{ showDrawerPrimary?: boolean }>`
   display: flex;
   flex-flow: column nowrap;
   justify-content: flex-start;

...
@@ -177,6 +209,16 @@ const StyledChildrenWrapper = styled.div`
 
   flex: 1;
   width: 100%;
+
+  ${({ showDrawerPrimary = false }) =>
+    showDrawerPrimary &&
+    css`
+      max-width: calc(100% - 230px);
+    `};
+
+  @media only screen and (max-width: 768px) {
+    max-width: 100%;
+  }
 `;
 
 const StyledFooterWrapper = styled.div`

app/components/PageHeader.tsx
@@ -9,11 +9,14 @@ import { NamedColors } from "../utils/style";
 import { buildRouteLink } from "../utils/shared";
 import { PageWrapper } from "./PageWrapper";
 
-interface PageHeaderProps extends CommonProps {}
+interface PageHeaderProps extends CommonProps {
+  forceShowLogo?: boolean;
+}
 
 export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
   commonProps,
   themeScheme,
+  forceShowLogo = true,
 }) => {
   const invertThemeScheme = themeScheme === "light" ? "dark" : "light";
 

...
@@ -23,15 +26,19 @@ export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
         <>
           <a
             aria-label={"View your profile and repositories"}
+            title={`View @${commonProps.currentUserUsername || "ghost"} profile and settings`}
             href={buildRouteLink(
               AppRoute.USER_DETAILS,
               {
                 username: commonProps.currentUserUsername || "ghost",
               },
-              { encodeURIComponent: false }
+              { encodeURIComponent: false },
             )}
           >
-            {commonProps.currentUserUsername || "ghost"}
+            <PageHeaderAvatar
+              aria-label={commonProps.currentUserUsername || "ghost"}
+              themeScheme={themeScheme}
+            />
           </a>
         </>
       );

...
@@ -58,26 +65,51 @@ export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
   return (
     <StyledPageHeader themeScheme={themeScheme}>
       <PageWrapper>
-        <StyledLogoArea themeScheme={themeScheme}>
+        <StyledLogoArea themeScheme={themeScheme} forceShowLogo={forceShowLogo}>
           <a href={"/"}>
-            <h1 style={{ margin: 0 }}>{Const.APP_NAME}</h1>
+            <h1>{Const.APP_NAME}</h1>
           </a>
         </StyledLogoArea>
-        <StyledPageHeaderNav>
+        <StyledPageHeaderNav themeScheme={themeScheme}>
           <a
             aria-label={"Explore Repositories"}
             href={buildRouteLink(AppRoute.REPOSITORY_EXPLORE, null)}
+            className={
+              commonProps.path ===
+              buildRouteLink(AppRoute.REPOSITORY_EXPLORE, null)
+                ? "active"
+                : undefined
+            }
           >
             Explore
           </a>
+          <a
+            aria-label={"Contribute to GitFOSS development"}
+            href={buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
+              orgSlug: "ethicdevs",
+              repoSlug: "gitfoss",
+            })}
+            className={
+              commonProps.path ===
+              buildRouteLink(AppRoute.REPOSITORY_DETAILS, {
+                orgSlug: "ethicdevs",
+                repoSlug: "gitfoss",
+              })
+                ? "active"
+                : undefined
+            }
+          >
+            Contribute
+          </a>
         </StyledPageHeaderNav>
+        <div style={{ flex: 1 }} />
         <StyledActionsArea>
           {commonProps.authenticated && (
             <a
               aria-label={"Create a new Repository"}
               href={buildRouteLink(AppRoute.REPOSITORY_CREATE, null)}
             >
-              New Repository
+              (+) Repo
             </a>
           )}
           <a

...
@@ -88,7 +120,7 @@ export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
             })}
             title={`Click to enable ${invertThemeScheme} mode`}
           >
-            {`${themeScheme === "light" ? "Dark" : "Light"} mode`}
+            {`${themeScheme === "light" ? "Dark" : "Light"}`}
           </a>
           {pageHeaderActions}
         </StyledActionsArea>

...
@@ -106,6 +138,13 @@ const StyledPageHeader = styled.header<WithThemeSchemeProp>`
   height: 100%;
   width: 100%;
 
+  /* above mobile size */
+  @media only screen and (min-width: 768px) {
+    & > ${PageWrapper} {
+      padding: 0 16px;
+    }
+  }
+
   & > ${PageWrapper} {
     height: 100%;
 

...
@@ -113,7 +152,7 @@ const StyledPageHeader = styled.header<WithThemeSchemeProp>`
     justify-content: flex-start;
     align-items: center;
 
-    padding: 0 16px;
+    padding: 0;
     gap: 16px;
   }
 

...
@@ -134,7 +173,23 @@ const StyledPageHeader = styled.header<WithThemeSchemeProp>`
   }
 `;
 
-const StyledLogoArea = styled.div<WithThemeSchemeProp>`
+const StyledLogoArea = styled.div<
+  WithThemeSchemeProp & { forceShowLogo: boolean }
+>`
+  ${({ forceShowLogo }) =>
+    forceShowLogo !== true &&
+    css`
+      @media only screen and (min-width: 768px) {
+        display: none;
+      }
+    `};
+
+  @media only screen and (max-width: 768px) {
+    & > a > h1 {
+      font-size: 22px;
+    }
+  }
+
   & > a {
     display: flex;
     flex-flow: row nowrap;

...
@@ -144,24 +199,64 @@ const StyledLogoArea = styled.div<WithThemeSchemeProp>`
     ${({ themeScheme }) => css`
       color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
     `};
+
+    h1 {
+      margin: 0;
+    }
   }
 `;
 
-const StyledPageHeaderNav = styled.nav`
+const StyledPageHeaderNav = styled.nav<WithThemeSchemeProp>`
   display: flex;
   flex-flow: row nowrap;
   justify-content: flex-start;
   align-items: center;
 
-  flex: 1;
-  height: 100%;
-  width: 100%;
+  /* flex: 1; */
+  height: 40px;
+  /* width: 100%; */
+
+  gap: 2px;
+  margin: 0;
 
-  gap: 24px;
-  margin-left: 4px;
+  ${({ themeScheme }) => css`
+    border-radius: 20px;
+    background-color: ${NamedColors.CARD_OVERLAY[themeScheme]};
+  `};
+
+  @media only screen and (max-width: 768px) {
+    display: none;
+  }
 
   & > a {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
     white-space: nowrap;
+
+    height: 40px;
+    padding: 0 16px;
+
+    border-radius: 20px;
+    font-weight: normal;
+    text-decoration: none;
+
+    ${({ themeScheme }) => css`
+      color: ${NamedColors.TEXT_MUTED[themeScheme]};
+      /* background-color: ${NamedColors.CARD[themeScheme]}; */
+    `};
+
+    &.active,
+    &:hover {
+      ${({ themeScheme }) => css`
+        color: ${NamedColors.TEXT_DEFAULT[themeScheme]};
+        background-color: ${NamedColors.CARD[themeScheme]};
+        font-weight: bold;
+        font-family: monospace;
+        text-decoration: none;
+      `};
+    }
   }
 `;
 

...
@@ -176,3 +271,16 @@ const StyledActionsArea = styled.div`
     white-space: nowrap;
   }
 `;
+
+const PageHeaderAvatar = styled.img<WithThemeSchemeProp>`
+  width: 40px;
+  height: 40px;
+
+  border-image: none;
+  border-radius: 40px;
+
+  ${({ themeScheme }) => css`
+    border: 1px solid ${NamedColors.BORDER_CARD[themeScheme]};
+    background-color: ${NamedColors.CARD_OVERLAY[themeScheme]};
+  `};
+`;

app/islands/RepositoryHero.tsx
@@ -53,7 +53,7 @@ const RepositoryHero: ReactIsland<RepositoryHeroProps> = ({
             </a>
             {` ${separator} `}
             {path == null ? (
-              <span style={{ textTransform: "capitalize" }}>
+              <span style={{ textTransform: "capitalize", fontSize: 16 }}>
                 ({repo.visibility.toLowerCase()})
               </span>
             ) : (

@@ -37,6 +37,7 @@ export interface CommonViewProps {
   flashMessage: string | null;
   themeScheme: AppThemeScheme;
   title?: string;
+  path?: string;
 }
 
 export type CommonProps = { commonProps: CommonViewProps };

app/utils/server/makeRequestHandler.ts
@@ -49,6 +49,7 @@ export const makeRequestHandler = {
             gitStamp: request.gitStamp,
             themeScheme,
             title,
+            path: request.url,
           },
         } as T & { commonProps: CommonViewProps };
 

app/views/InternalErrorView.tsx
@@ -15,10 +15,10 @@ export interface InternalErrorViewProps extends CommonProps {
   error: FastifyError;
 }
 
-const DEV = process.env.NODE_ENV === "development";
-const DEBUG = !!(
-  process.env.DEBUG != null && ["true", "1", true].includes(process.env.DEBUG)
-);
+// const DEV = process.env.NODE_ENV === "development";
+// const DEBUG = !!(
+//   process.env.DEBUG != null && ["true", "1", true].includes(process.env.DEBUG)
+// );
 
 const InternalErrorView: ReactView<InternalErrorViewProps> = ({
   commonProps,

...
@@ -55,8 +55,7 @@ const InternalErrorView: ReactView<InternalErrorViewProps> = ({
         )}
         {isInternalError && (
           <h1>
-            😵‍💫 Woops... we've encountered an internal error, please
-            apologize.
+            😵‍💫 Woops... we've encountered an internal error, please apologize.
           </h1>
         )}
         <div style={{ marginTop: 8 }}>

...
@@ -69,66 +68,66 @@ const InternalErrorView: ReactView<InternalErrorViewProps> = ({
               <p>Sorry but it is not possible to recover from this error.</p>
             ))}
         </div>
-        {(DEBUG || DEV) && (
-          <div style={{ marginTop: 24 }}>
-            {getThemedCodeCss(commonProps.themeScheme)}
-            <details open>
-              <summary>[DEBUG] Full error details:</summary>
-              {message != null && message.trim() !== "" && (
-                <div style={{ marginTop: 16 }}>
-                  <label
-                    style={{ fontWeight: "bold", textDecoration: "underline" }}
-                  >
-                    Message:
-                  </label>
-                  <pre>
-                    <code>{message.trim()}</code>
-                  </pre>
-                </div>
-              )}
-              {stack != null && stack.trim() !== "" && (
-                <div style={{ marginTop: 16 }}>
-                  <label
-                    style={{ fontWeight: "bold", textDecoration: "underline" }}
-                  >
-                    Stack:
-                  </label>
-                  <Card
-                    data-islandid={`${Code.name}$$0`}
-                    style={{ width: "100%", marginTop: 32 }}
+        {/* {(DEBUG || DEV) && ( */}
+        <div style={{ maxWidth: "100%", marginTop: 24 }}>
+          {getThemedCodeCss(commonProps.themeScheme)}
+          <details open>
+            <summary>[DEBUG] Full error details:</summary>
+            {message != null && message.trim() !== "" && (
+              <div style={{ marginTop: 16 }}>
+                <label
+                  style={{ fontWeight: "bold", textDecoration: "underline" }}
+                >
+                  Message:
+                </label>
+                <pre>
+                  <code style={{whiteSpace:'pre-wrap'}}>{message.trim()}</code>
+                </pre>
+              </div>
+            )}
+            {stack != null && stack.trim() !== "" && (
+              <div style={{ marginTop: 16 }}>
+                <label
+                  style={{ fontWeight: "bold", textDecoration: "underline" }}
+                >
+                  Stack:
+                </label>
+                <Card
+                  data-islandid={`${Code.name}$$0`}
+                  style={{ width: "100%", marginTop: 32 }}
+                  themeScheme={commonProps.themeScheme}
+                >
+                  <Code
+                    language={"python"}
+                    code={stack.replace(message, "").trim()}
                     themeScheme={commonProps.themeScheme}
-                  >
-                    <Code
-                      language={"python"}
-                      code={stack.replace(message, "").trim()}
-                      themeScheme={commonProps.themeScheme}
-                    />
-                  </Card>
-                </div>
-              )}
-              {validation != null && (
-                <div style={{ marginTop: 16 }}>
-                  <label
-                    style={{ fontWeight: "bold", textDecoration: "underline" }}
-                  >
-                    Validation:
-                  </label>
-                  <Card
-                    data-islandid={`${Code.name}$$1`}
-                    style={{ width: "100%", marginTop: 32 }}
+                  />
+                </Card>
+              </div>
+            )}
+            {validation != null && (
+              <div style={{ marginTop: 16 }}>
+                <label
+                  style={{ fontWeight: "bold", textDecoration: "underline" }}
+                >
+                  Validation:
+                </label>
+                <Card
+                  data-islandid={`${Code.name}$$1`}
+                  style={{ width: "100%", marginTop: 32 }}
+                  themeScheme={commonProps.themeScheme}
+                >
+                  <Code
+                    language={"json"}
+                    code={JSON.stringify(validation, null, 2)}
                     themeScheme={commonProps.themeScheme}
-                  >
-                    <Code
-                      language={"json"}
-                      code={JSON.stringify(validation, null, 2)}
-                      themeScheme={commonProps.themeScheme}
-                    />
-                  </Card>
-                </div>
-              )}
-            </details>
-          </div>
-        )}
+                  />
+                </Card>
+              </div>
+            )}
+          </details>
+        </div>
+        {/* )} */}
       </PageWrapper>
     </Layout>
   );

app/views/repository/RepositoryBrowserView.tsx
@@ -56,10 +56,14 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
   const currPathParts = path.split("/");
   const shouldShowRootPath = path !== "/";
 
-  console.log("currentRef:", currentRef);
-
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+      currentRef={currentRef}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

app/views/repository/RepositoryCommitsLogView.tsx
@@ -36,7 +36,13 @@ const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
   repo,
 }) => {
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+      currentRef={currentRef}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

app/views/repository/RepositoryCompareView.tsx
@@ -33,7 +33,13 @@ const RepositoryCompareView: ReactView<RepositoryCompareViewProps> = ({
   refB,
 }) => {
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+      currentRef={refA}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

app/views/repository/RepositoryDetailsView.tsx
@@ -66,7 +66,13 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
 }) => {
   const { forkedFromRepo, ...repo } = repoWithForkedFromRepoMetas;
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+      currentRef={currentRef}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

...
@@ -220,7 +226,7 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
                           <span>{keyword}</span>
                           {idx === self.length - 1 ? "." : ", "}
                         </React.Fragment>
-                      )
+                      ),
                   )}
                 </p>
               )}

...
@@ -273,14 +279,14 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
                                     path !== "/"
                                       ? path
                                       : "",
-                                }
+                                },
                               )}
                             >
                               {branch}
                             </a>
                             {idx === self.length - 1 ? "." : ", "}
                           </React.Fragment>
-                        )
+                        ),
                     )}
                   </p>
                 )}

...
@@ -305,7 +311,7 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
                             <span>{tag}</span>
                             {idx === self.length - 1 ? "." : ", "}
                           </React.Fragment>
-                        )
+                        ),
                     )}
                   </p>
                 )}

app/views/repository/RepositoryForkView.tsx
@@ -35,7 +35,12 @@ const RepositoryForkView: ReactView<RepositoryForkViewProps> = ({
   initialValues = undefined,
 }) => {
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={sourceParentOrg.slug}
+      repoSlug={sourceRepo.slug}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

app/views/repository/RepositoryShowObjectView.tsx
@@ -43,15 +43,21 @@ const RepositoryShowObjectView: ReactView<RepositoryShowObjectViewProps> = ({
 }) => {
   const totalAdditions = gitObjectDiffs?.reduce(
     (acc, obj) => (acc += obj.additions),
-    0
+    0,
   );
   const totalDeletions = gitObjectDiffs?.reduce(
     (acc, obj) => (acc += obj.deletions),
-    0
+    0,
   );
 
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+      currentRef={currentRef}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

app/views/repositoryPullRequests/RepositoryPullRequestCreateView.tsx
@@ -21,35 +21,41 @@ export interface RepositoryPullRequestCreateViewProps extends CommonProps {
   variant: RepositoryPullRequestCreateFormVariant;
 }
 
-const RepositoryPullRequestCreateView: ReactView<RepositoryPullRequestCreateViewProps> =
-  ({ commonProps, parentOrg, repo, variant }) => {
-    return (
-      <Layout {...commonProps}>
-        <PageWrapper>
-          <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
-            <RepositoryHero
-              forkedFromRepo={repo.forkedFromRepo}
-              forksCount={repo.forks.length}
-              parentOrg={parentOrg}
-              path={`Pull Requests / New`}
-              repo={repo}
-            />
-          </IslandWrapper>
-          <IslandWrapper
-            data-islandid={`${RepositoryPullRequestCreateForm.name}$$0`}
-            style={{ marginTop: 24 }}
-          >
-            <RepositoryPullRequestCreateForm
-              parentOrgSlug={parentOrg.slug}
-              repoSlug={repo.slug}
-              themeScheme={commonProps.themeScheme}
-              variant={variant}
-            />
-          </IslandWrapper>
-        </PageWrapper>
-      </Layout>
-    );
-  };
+const RepositoryPullRequestCreateView: ReactView<
+  RepositoryPullRequestCreateViewProps
+> = ({ commonProps, parentOrg, repo, variant }) => {
+  return (
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+    >
+      <PageWrapper>
+        <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
+          <RepositoryHero
+            forkedFromRepo={repo.forkedFromRepo}
+            forksCount={repo.forks.length}
+            parentOrg={parentOrg}
+            path={`Pull Requests / New`}
+            repo={repo}
+          />
+        </IslandWrapper>
+        <IslandWrapper
+          data-islandid={`${RepositoryPullRequestCreateForm.name}$$0`}
+          style={{ marginTop: 24 }}
+        >
+          <RepositoryPullRequestCreateForm
+            parentOrgSlug={parentOrg.slug}
+            repoSlug={repo.slug}
+            themeScheme={commonProps.themeScheme}
+            variant={variant}
+          />
+        </IslandWrapper>
+      </PageWrapper>
+    </Layout>
+  );
+};
 
 RepositoryPullRequestCreateView.displayName = "RepositoryPullRequestCreateView";
 export default RepositoryPullRequestCreateView;

app/views/repositoryPullRequests/RepositoryPullRequestDetailsView.tsx
@@ -68,7 +68,7 @@ const RepositoryPullRequestDetailsView: ReactView<
       };
       return acc;
     },
-    { additions: 0, deletions: 0 }
+    { additions: 0, deletions: 0 },
   );
 
   if (pr.state !== PullRequestState.OPEN) {

...
@@ -76,7 +76,12 @@ const RepositoryPullRequestDetailsView: ReactView<
   }
 
   return (
-    <Layout {...commonProps}>
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+    >
       <PageWrapper>
         <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
           <RepositoryHero

...
@@ -110,7 +115,7 @@ const RepositoryPullRequestDetailsView: ReactView<
                   {
                     username: prAuthor.username,
                   },
-                  { encodeURIComponent: false }
+                  { encodeURIComponent: false },
                 )}
               >
                 {prAuthor.displayName || prAuthor.username}

...
@@ -124,7 +129,7 @@ const RepositoryPullRequestDetailsView: ReactView<
                         orgSlug: parentOrg.slug,
                         repoSlug: repo.slug,
                         pullUid: pr.uid,
-                      }
+                      },
                     )}
                   >
                     Edit PR

...
@@ -136,7 +141,7 @@ const RepositoryPullRequestDetailsView: ReactView<
                         orgSlug: parentOrg.slug,
                         repoSlug: repo.slug,
                         pullUid: pr.uid,
-                      }
+                      },
                     )}
                   >
                     Delete PR

...
@@ -208,7 +213,7 @@ const RepositoryPullRequestDetailsView: ReactView<
                       orgSlug: parentOrg.slug,
                       repoSlug: repo.slug,
                       pullUid: pr.uid,
-                    }
+                    },
                   )}
                 >
                   <Grid.Col fluid nowrap gap={8}>

app/views/repositoryPullRequests/RepositoryPullRequestsView.tsx
@@ -21,163 +21,169 @@ export interface RepositoryPullRequestsViewProps extends CommonProps {
   pullRequestsFilter?: PullRequestsFilter;
 }
 
-const RepositoryPullRequestsView: ReactView<RepositoryPullRequestsViewProps> =
-  ({
-    commonProps,
-    parentOrg,
-    pullRequests,
-    repo,
-    pullRequestsFilter = "all",
-  }) => {
-    return (
-      <Layout {...commonProps}>
-        <PageWrapper>
-          <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
-            <RepositoryHero
-              forkedFromRepo={repo.forkedFromRepo}
-              forksCount={repo.forks.length}
-              parentOrg={parentOrg}
-              path={`Pull Requests`}
-              repo={repo}
-            />
-          </IslandWrapper>
-          <Grid.Col fluid style={{ marginTop: 32 }}>
+const RepositoryPullRequestsView: ReactView<
+  RepositoryPullRequestsViewProps
+> = ({
+  commonProps,
+  parentOrg,
+  pullRequests,
+  repo,
+  pullRequestsFilter = "all",
+}) => {
+  return (
+    <Layout
+      {...commonProps}
+      showDrawerPrimary
+      orgSlug={parentOrg.slug}
+      repoSlug={repo.slug}
+    >
+      <PageWrapper>
+        <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
+          <RepositoryHero
+            forkedFromRepo={repo.forkedFromRepo}
+            forksCount={repo.forks.length}
+            parentOrg={parentOrg}
+            path={`Pull Requests`}
+            repo={repo}
+          />
+        </IslandWrapper>
+        <Grid.Col fluid style={{ marginTop: 32 }}>
+          <a
+            href={buildRouteLink(AppRoute.REPOSITORY_PULL_REQUEST_CREATE, {
+              orgSlug: parentOrg.slug,
+              repoSlug: repo.slug,
+            })}
+          >
+            New Pull Request
+          </a>
+        </Grid.Col>
+        <Grid.Col fluid style={{ marginTop: 24 }}>
+          <Grid.Row fluid alignItems={"center"} gap={16}>
             <a
-              href={buildRouteLink(AppRoute.REPOSITORY_PULL_REQUEST_CREATE, {
-                orgSlug: parentOrg.slug,
-                repoSlug: repo.slug,
-              })}
+              style={{
+                textDecoration:
+                  pullRequestsFilter === "all" ? "underline" : "none",
+              }}
+              href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
+                "all" as PullRequestsFilter
+              }`}
             >
-              New Pull Request
+              All
             </a>
-          </Grid.Col>
-          <Grid.Col fluid style={{ marginTop: 24 }}>
-            <Grid.Row fluid alignItems={"center"} gap={16}>
-              <a
-                style={{
-                  textDecoration:
-                    pullRequestsFilter === "all" ? "underline" : "none",
-                }}
-                href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
-                  "all" as PullRequestsFilter
-                }`}
-              >
-                All
-              </a>
-              <a
-                style={{
-                  textDecoration:
-                    pullRequestsFilter === "opened" ? "underline" : "none",
-                }}
-                href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
-                  "opened" as PullRequestsFilter
-                }`}
-              >
-                Opened
-              </a>
-              <a
-                style={{
-                  textDecoration:
-                    pullRequestsFilter === "merged" ? "underline" : "none",
-                }}
-                href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
-                  "merged" as PullRequestsFilter
-                }`}
-              >
-                Merged
-              </a>
-              <a
-                style={{
-                  textDecoration:
-                    pullRequestsFilter === "closed" ? "underline" : "none",
-                }}
-                href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
-                  "closed" as PullRequestsFilter
-                }`}
+            <a
+              style={{
+                textDecoration:
+                  pullRequestsFilter === "opened" ? "underline" : "none",
+              }}
+              href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
+                "opened" as PullRequestsFilter
+              }`}
+            >
+              Opened
+            </a>
+            <a
+              style={{
+                textDecoration:
+                  pullRequestsFilter === "merged" ? "underline" : "none",
+              }}
+              href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
+                "merged" as PullRequestsFilter
+              }`}
+            >
+              Merged
+            </a>
+            <a
+              style={{
+                textDecoration:
+                  pullRequestsFilter === "closed" ? "underline" : "none",
+              }}
+              href={`/${parentOrg.slug}/${repo.slug}/pulls?filter=${
+                "closed" as PullRequestsFilter
+              }`}
+            >
+              Closed
+            </a>
+          </Grid.Row>
+        </Grid.Col>
+        <Grid.Col fluid style={{ marginTop: 32 }}>
+          {pullRequests != null && pullRequests.length >= 1 ? (
+            pullRequests.map((pr, idx) => (
+              <Grid.Col
+                key={pr.id}
+                fluid
+                style={{ marginTop: idx === 0 ? 0 : 16 }}
               >
-                Closed
-              </a>
-            </Grid.Row>
-          </Grid.Col>
-          <Grid.Col fluid style={{ marginTop: 32 }}>
-            {pullRequests != null && pullRequests.length >= 1 ? (
-              pullRequests.map((pr, idx) => (
-                <Grid.Col
-                  key={pr.id}
+                <a
+                  href={buildRouteLink(
+                    AppRoute.REPOSITORY_PULL_REQUEST_DETAILS,
+                    {
+                      orgSlug: parentOrg.slug,
+                      repoSlug: repo.slug,
+                      pullUid: pr.uid,
+                    },
+                  )}
+                >
+                  #{pr.uid} - {pr.summary} [{pr.state}]
+                </a>
+                <span style={{ opacity: 0.67 }}>
+                  wants to merge <code>{pr.sourceBranch}</code> into{" "}
+                  <code>{pr.targetBranch}</code>
+                </span>
+                <Grid.Row
                   fluid
-                  style={{ marginTop: idx === 0 ? 0 : 16 }}
+                  alignItems={"center"}
+                  style={{ opacity: 0.67, marginTop: 4 }}
+                >
+                  {new Date(pr.createdAt).getTime() <=
+                    new Date(pr.updatedAt).getTime() && (
+                    <span>
+                      opened on {new Date(pr.createdAt).toLocaleString()}
+                    </span>
+                  )}
+                  {((pr.closedAt == null &&
+                    new Date(pr.updatedAt).getTime() >
+                      new Date(pr.createdAt).getTime()) ||
+                    (pr.closedAt != null &&
+                      new Date(pr.updatedAt).getTime() <
+                        new Date(pr.closedAt).getTime())) && (
+                    <span>
+                      updated on {new Date(pr.updatedAt).toLocaleString()}
+                    </span>
+                  )}
+                  {pr.closedAt != null && (
+                    <span>
+                      closed on
+                      {new Date(pr.closedAt).toLocaleString()}
+                    </span>
+                  )}
+                </Grid.Row>
+              </Grid.Col>
+            ))
+          ) : (
+            <div>
+              <h1>No Pull Request Yet</h1>
+              <p>
+                <span>Be the change you want to see, </span>
+                <a
+                  href={buildRouteLink(
+                    AppRoute.REPOSITORY_PULL_REQUEST_CREATE,
+                    {
+                      orgSlug: parentOrg.slug,
+                      repoSlug: repo.slug,
+                    },
+                  )}
                 >
-                  <a
-                    href={buildRouteLink(
-                      AppRoute.REPOSITORY_PULL_REQUEST_DETAILS,
-                      {
-                        orgSlug: parentOrg.slug,
-                        repoSlug: repo.slug,
-                        pullUid: pr.uid,
-                      }
-                    )}
-                  >
-                    #{pr.uid} - {pr.summary} [{pr.state}]
-                  </a>
-                  <span style={{ opacity: 0.67 }}>
-                    wants to merge <code>{pr.sourceBranch}</code> into{" "}
-                    <code>{pr.targetBranch}</code>
-                  </span>
-                  <Grid.Row
-                    fluid
-                    alignItems={"center"}
-                    style={{ opacity: 0.67, marginTop: 4 }}
-                  >
-                    {new Date(pr.createdAt).getTime() <=
-                      new Date(pr.updatedAt).getTime() && (
-                      <span>
-                        opened on {new Date(pr.createdAt).toLocaleString()}
-                      </span>
-                    )}
-                    {((pr.closedAt == null &&
-                      new Date(pr.updatedAt).getTime() >
-                        new Date(pr.createdAt).getTime()) ||
-                      (pr.closedAt != null &&
-                        new Date(pr.updatedAt).getTime() <
-                          new Date(pr.closedAt).getTime())) && (
-                      <span>
-                        updated on {new Date(pr.updatedAt).toLocaleString()}
-                      </span>
-                    )}
-                    {pr.closedAt != null && (
-                      <span>
-                        closed on
-                        {new Date(pr.closedAt).toLocaleString()}
-                      </span>
-                    )}
-                  </Grid.Row>
-                </Grid.Col>
-              ))
-            ) : (
-              <div>
-                <h1>No Pull Request Yet</h1>
-                <p>
-                  <span>Be the change you want to see, </span>
-                  <a
-                    href={buildRouteLink(
-                      AppRoute.REPOSITORY_PULL_REQUEST_CREATE,
-                      {
-                        orgSlug: parentOrg.slug,
-                        repoSlug: repo.slug,
-                      }
-                    )}
-                  >
-                    open the first Pull Request
-                  </a>
-                  <span> to this repository 🚀.</span>
-                </p>
-              </div>
-            )}
-          </Grid.Col>
-        </PageWrapper>
-      </Layout>
-    );
-  };
+                  open the first Pull Request
+                </a>
+                <span> to this repository 🚀.</span>
+              </p>
+            </div>
+          )}
+        </Grid.Col>
+      </PageWrapper>
+    </Layout>
+  );
+};
 
 RepositoryPullRequestsView.displayName = "RepositoryPullRequestsView";
 export default RepositoryPullRequestsView;

data/http_client.d.ts
@@ -1,38 +1,36 @@
-declare module "./http_client" {
-  import { IncomingMessage } from "http";
+import { IncomingMessage } from "http";
 
-  export class HttpResponse {
-    readonly statusCode: number;
-    readonly statusText: string;
-    readonly ok: boolean;
-    readonly headers: IncomingMessage["headers"];
+export class HttpResponse {
+  readonly statusCode: number;
+  readonly statusText: string;
+  readonly ok: boolean;
+  readonly headers: IncomingMessage["headers"];
 
-    constructor(incoming: IncomingMessage);
+  constructor(incoming: IncomingMessage);
 
-    text(): Promise<string>;
-    isJson(): Promise<boolean>;
-    json(): Promise<any>;
-  }
+  text(): Promise<string>;
+  isJson(): Promise<boolean>;
+  json<T extends any = any>(): Promise<T>;
+}
 
-  export interface RequestConfig {
-    headers?: Record<string, string>;
-    body?: string | Buffer | any;
-  }
+export interface RequestConfig {
+  headers?: Record<string, string>;
+  body?: string | Buffer | any;
+}
 
-  export class HttpClient {
-    constructor();
+export class HttpClient {
+  constructor();
 
-    get(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    post(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    put(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    patch(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    delete(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    head(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    options(url: string, config?: RequestConfig): Promise<HttpResponse>;
-    custom(
-      method: string,
-      url: string,
-      config?: RequestConfig,
-    ): Promise<HttpResponse>;
-  }
+  get(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  post(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  put(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  patch(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  delete(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  head(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  options(url: string, config?: RequestConfig): Promise<HttpResponse>;
+  custom(
+    method: string,
+    url: string,
+    config?: RequestConfig,
+  ): Promise<HttpResponse>;
 }

@@ -1,39 +1,85 @@
 #!/bin/sh
-SSH_ORIGINAL_COMMAND=${SSH_ORIGINAL_COMMAND}
 
+# Passed in the environment by ssh force_command:
+# environment="KEY_ID=...",command="/usr/bin/ssh_command ..."
+# KEY_ID="..." /usr/bin/ssh_command ...
+KEY_ID=${KEY_ID:-unset}
+
+if [ -z ${KEY_ID} ] 2>/dev/null ||
+   [ -n ${KEY_ID} ] 2>/dev/null ||
+   [ "${KEY_ID}" = "unset" ] 2>/dev/null; then
+  printf '%s\n' "Could not authorize command. KEY_ID is not set/empty."
+  exit 128
+fi
+
+# Passed as first argument by ssh force_command:
+# /usr/bin/ssh_command $1
 USERNAME=$1
 
+if [ -z ${USERNAME} ] 2>/dev/null ||
+   [ -n ${USERNAME} ] 2>/dev/null ||
+   [ "${USERNAME}" = "unset" ] 2>/dev/null; then
+  printf '%s\n' "Could not authorize command. KEY_ID is not set/empty."
+  exit 128
+fi
+
+SSH_ORIGINAL_COMMAND=${SSH_ORIGINAL_COMMAND:-unset}
+
 # If SSH_ORIGINAL_COMMAND is unset, or empty, this was not invoked by ssh ForceCommand, kill now.
 # If USERNAME is unset, this was not invoked by ssh ForceCommand, kill now.
 if [ -z ${SSH_ORIGINAL_COMMAND+x} ] 2>/dev/null ||
   [ -n ${SSH_ORIGINAL_COMMAND} ] 2>/dev/null ||
-  [ -z ${USERNAME+x} ] 2>/dev/null; then
+  [ "${SSH_ORIGINAL_COMMAND}" = "unset" ] 2>/dev/null ; then
   printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
   printf '%s\n' "provide interactive shell access."
   exit 128
 fi
 
-USERNAME=$1
-RES_JSON=$(/usr/bin/ssh_command_node "${USERNAME}")
+RES_JSON=$(/usr/bin/ssh_command_node "${USERNAME}" "${KEY_ID}" "${SSH_ORIGINAL_COMMAND}")
 EXIT=$?
 
-echo "===> ${RES_JSON}\n"  >> /opt/ssh_commands.log
+# That's all we need to log;
+echo <<-EOF
+[git_ssh.connection.command]:
+⋗ time: $(TZ="Europe/Paris" date)
+⋗ user: ${USERNAME} (key: ${KEY_ID})
+⋗ command (original): ${SSH_ORIGINAL_COMMAND}
+EOF >> /opt/ssh_commands.log
+
+if [ "${EXIT}" != "0"]; then
+  printf '%s\n' "ssh_command_node exited with failure."
+  exit $EXIT
+fi
 
-echo "result => (${EXIT})\n-----------\n" >> /opt/ssh_commands.log
+# {
+#   COMMAND=$(echo "$RES_JSON" | jq -r '.command')
+#   AUTH_MODE=$(echo "$RES_JSON" | jq -r '.authMode')
+#   GIT_REPO_DIR=$(echo "$RES_JSON" | jq -r '.gitRepositoryDir')
+# } || {
+COMMAND=${SSH_ORIGINAL_COMMAND}
+AUTH_MODE="always"
+GIT_REPO_DIR="unset"
+# }
 
-COMMAND=$(echo "$RES_JSON" | jq -r '.command')
-AUTH_MODE=$(echo "$RES_JSON" | jq -r '.authMode')
-GIT_REPO_DIR=$(echo "$RES_JSON" | jq -r '.gitRepositoryDir')
+echo <<-EOF
+⋗ command (parsed): ${SSH_ORIGINAL_COMMAND}
+⋗ auth mode: ${AUTH_MODE}
+⋗ repo path: ${GIT_REPO_DIR}
+EOF >> /opt/ssh_commands.log
 
-echo "AUTH_MODE: ${AUTH_MODE}" >> /opt/ssh_commands.log
-echo "GIT_REPO_DIR: ${GIT_REPO_DIR}" >> /opt/ssh_commands.log
-echo "ssh_command_node stdout: ${RES_JSON}" >> /opt/ssh_commands.log
-echo "ssh_command_node exit code: ${EXIT}" >> /opt/ssh_commands.log
+# echo <<-EOF
+# ⋗ ssh key fingerprint: 11bca03df28f0a2f95a8a11
+# ⋗ gitfoss key fingerprint: 11bca03df28f0a2f95a8a11
+# ⋗ match?: YES | NO
+# EOF >> /opt/ssh_commands.log
 
+# auth passed, execute git command (safe)
 if [ "$EXIT" = "0" ]; then
-  LANG=C $COMMAND $GIT_REPO_DIR;
+  echo "⋗ authorized?: YES (Call original command)\n\n" >> /opt/ssh_commands.log
+  COMMAND_OUTPUT=$(LANG=C $COMMAND $GIT_REPO_DIR);
   exit $?
 else
+  echo "⋗ authorized?: NO (Forbidden access)\n\n" >> /opt/ssh_commands.log
   echo "Forbidden access.\n"
   exit 1
 fi

data/ssh_command_node
@@ -1,144 +1,116 @@
 #!/usr/bin/node
-
-const { HttpClient } = require("./http_client");
-// const { HttpClient } = require("/home/debian/http_client.js");
 const fs = require("fs");
-
-const LOGS_FILE = "/opt/ssh_commands.log";
-
-function log(message, ...args) {
-  try {
-    fs.appendFileSync(
-      LOGS_FILE,
-      JSON.stringify({
-        timestamp: new Date().toISOString(),
-        message,
-        args,
-      }) + "\n",
-      { encoding: "utf8" },
-    );
-  } catch (err) {
-    // console.log(message, ...args);
-  }
-}
+const { HttpClient } = require("./http_client");
 
 async function main(args, sshOriginalCommand) {
   const [_, __, username] = args;
 
   if (username == null || username.trim() === "") {
+    console.log(JSON.stringify({ success: false }));
     process.exit(128);
   }
 
   if (sshOriginalCommand == null) {
+    console.log(JSON.stringify({ success: false }));
     process.exit(128);
   }
 
-  const authorizedKeysBuffer = fs.readFileSync(
-    "/home/git/.ssh/authorized_keys",
-    { encoding: "utf8" },
-  );
-
-  const authKeys = authorizedKeysBuffer
-    .split("\n")
-    .map((line) =>
-      line.startsWith("#")
-        ? { type: "comment", text: line }
-        : line.trim() !== ""
-          ? { type: "key", text: line.trim() }
-          : null,
-    )
-    .filter((x) => x != null && x.type === "key");
-
-  // console.log("authkeys:", authKeys);
-  // console.log("username", username);
-
-  let userPk = authKeys.find(
-    (key) =>
-      key.text.includes(`command="ssh_command ${username}"`) ||
-      key.text.includes(`command="/usr/bin/ssh_command ${username}"`),
-  );
-
-  if (userPk == null) {
-    log("No key matched ssh connection in authorized_keys file.", { username });
-    return process.exit(128);
-  }
+  try {
+    const authorizedKeysBuffer = fs.readFileSync(
+      "/home/git/.ssh/authorized_keys",
+      { encoding: "utf8" },
+    );
 
-  const pk = userPk.text;
-
-  const sshRsaIndex = pk.indexOf("ssh-rsa");
-  const publicKey = pk.substring(sshRsaIndex);
-
-  const [command, repoSlug] = sshOriginalCommand
-    .split(" ")
-    .map((part) => part.replace(/\'/g, "").trim());
-
-  const data = JSON.stringify({
-    command,
-    repoSlug,
-    username,
-    publicKey,
-  });
-
-  log("Will authenticate through /_ssh/auth", {
-    command,
-    repoSlug,
-    username,
-    publicKey,
-  });
-
-  const client = new HttpClient();
-
-  // console.log("HttpClient.instance:", client);
-
-  const res = await client.post("http://localhost:1337/_ssh/auth", {
-    headers: {
-      "Content-Type": "application/json",
-      "Content-Length": Buffer.byteLength(data),
-    },
-    body: data,
-  });
-
-  // console.log("res:", res);
-
-  if (res.ok === false) {
-    log(
-      "/_ssh/auth response is an error:",
-      res.statusCode,
-      res.statusText,
-      await res.text(),
+    const authKeys = authorizedKeysBuffer
+      .split("\n")
+      .map((line) =>
+        line.startsWith("#")
+          ? { type: "comment", text: line }
+          : line.trim() !== ""
+            ? { type: "key", text: line.trim() }
+            : null,
+      )
+      .filter((x) => x != null && x.type === "key");
+
+    const userPk = authKeys.find(
+      (key) =>
+        key.text.includes(`command="ssh_command ${username}"`) ||
+        key.text.includes(`command="/usr/bin/ssh_command ${username}"`),
     );
-    return process.exit(128);
-  }
 
-  if ((await res.isJson()) === false) {
-    log(
-      "/_ssh/auth response is not json:",
-      res.statusCode,
-      res.statusText,
-      await res.text(),
+    if (userPk == null) {
+      console.log(JSON.stringify({ success: false }));
+      return process.exit(128);
+    }
+
+    const pk = userPk.text;
+
+    const sshRsaIndex = pk.indexOf("ssh-rsa");
+    const publicKey = pk.substring(sshRsaIndex);
+
+    const [command, repoSlug] = sshOriginalCommand
+      .split(" ")
+      .map((part) => part.replace(/\'/g, "").trim());
+
+    const client = new HttpClient();
+
+    const data = JSON.stringify({
+      command,
+      repoSlug,
+      username,
+      publicKey,
+    });
+
+    // authenticate against live prod api by default.
+    const DEPLOYMENT_DOMAIN = process.env.DEPLOYMENT_DOMAIN || "127.0.0.1"; //|| "gitfoss.dev";
+    const DEPLOYMENT_SCHEME = process.env.DEPLOYMENT_SCHEME || "http"; //|| "https";
+    const PORT = process.env.PORT || 1337; //|| 443;
+    const PORT_STR = DEPLOYMENT_SCHEME === "https" ? "" : `:${PORT}`;
+    const SSH_AUTH_HELPER_URL = `${DEPLOYMENT_SCHEME}://${DEPLOYMENT_DOMAIN}${PORT_STR}/_ssh/auth`;
+
+    const res = await client.post(SSH_AUTH_HELPER_URL, {
+      headers: {
+        "Content-Type": "application/json",
+        "Content-Length": Buffer.byteLength(data),
+      },
+      body: data,
+    });
+
+    if (res.ok === false) {
+      console.log(JSON.stringify({ success: false }));
+      return process.exit(128);
+    }
+
+    if ((await res.isJson()) === false) {
+      console.log(JSON.stringify({ success: false }));
+      return process.exit(128);
+    }
+
+    const json = await res.json();
+
+    if (json.success === false) {
+      console.log(JSON.stringify({ success: false }));
+      return process.exit(128);
+    }
+  } catch (e) {
+    console.log(
+      JSON.stringify({
+        success: false,
+        error: e,
+      }),
     );
-    return process.exit(128);
+    process.exit(128);
   }
 
-  const json = await res.json();
+  // success!
+  // print only the json so ssh_command can parse and continue
 
-  log("/_ssh/auth response json:", res.statusCode, res.statusText, json);
+  const GIT_REPOSITORIES_ROOT =
+    process.env.GIT_REPOSITORIES_ROOT || "/var/lib/gitfoss/repos";
 
-  if (json.success === false) {
-    log(
-      "/_ssh/auth response is not successful:",
-      res.statusCode,
-      res.statusText,
-      json,
-    );
-    return process.exit(128);
-  }
+  json.gitRepositoryDir = `${GIT_REPOSITORIES_ROOT}${repoSlug.replace(/\.git$/, "")}.git`;
 
-  json.gitRepositoryDir = `/home/debian/data/gitfoss_repos/${repoSlug.replace(/\.git$/, "")}`;
-  console.log(JSON.stringify(json));
-
-  // success!
-  log("/_ssh/auth response success!", json);
+  console.log(JSON.stringify({ success: true, ...json }));
   process.exit(0);
 }
 

@@ -15,31 +15,31 @@ services:
       - 5432:5432
     volumes:
       - ./data/postgres_data:/var/lib/postgresql/data
-  web:
-    container_name: gitfoss_web
-    build:
-      context: .
-      dockerfile: Dockerfile
-      args:
-        - HOST=0.0.0.0
-        - PORT=1337
-    depends_on:
-      - db
-    ports:
-      - 1337:1337
-      - 22:22
-    volumes:
-      - ./data/gitfoss_repos:/var/lib/gitfoss/repos
-      - ./data/gitfoss_repos:/home/git/repos
-      - ./data/authorized_keys:/home/git/.ssh/authorized_keys
-    env_file: .env.docker
-    # environment:
-    #   - COOKIE_NAME=gitfoss_ssid
-    #   - COOKIE_SECRET=gitfoss-cookie-secret
-    #   - DATABASE_URL=postgresql://postgres:postgres@gitfoss_db:5432/gitfoss_local?sslmode=disable&connection_limit=3
-    #   - DEPLOYMENT_DOMAIN=local-app.localhost
-    #   - DEPLOYMENT_SCHEME=http
-    #   - GIT_REPOSITORIES_ROOT=/var/lib/gitfoss/repos
+  # web:
+  #   container_name: gitfoss_web
+  #   build:
+  #     context: .
+  #     dockerfile: Dockerfile
+  #     args:
+  #       - HOST=0.0.0.0
+  #       - PORT=1337
+  #   depends_on:
+  #     - db
+  #   ports:
+  #     - 1337:1337
+  #     - 22:22
+  #   volumes:
+  #     - ./data/gitfoss_repos:/var/lib/gitfoss/repos
+  #     - ./data/gitfoss_repos:/home/git/repos
+  #     - ./data/authorized_keys:/home/git/.ssh/authorized_keys
+  #   env_file: .env.docker
+  # environment:
+  #   - COOKIE_NAME=gitfoss_ssid
+  #   - COOKIE_SECRET=gitfoss-cookie-secret
+  #   - DATABASE_URL=postgresql://postgres:postgres@gitfoss_db:5432/gitfoss_local?sslmode=disable&connection_limit=3
+  #   - DEPLOYMENT_DOMAIN=local-app.localhost
+  #   - DEPLOYMENT_SCHEME=http
+  #   - GIT_REPOSITORIES_ROOT=/var/lib/gitfoss/repos
   # git_ssh:
   #   container_name: gitfoss_git_ssh
   #   build:

@@ -1,6 +1,7 @@
 todo:
 
-- [x] make ssh server work every times !!!!!
+- [x] make ssh server work
+- [ ] make ssh server work every times !!!!!
 - [x] make the islands runtime load dependencies properly
 - [ ] finish merge pull request feature
 - [x] add ssh key feature