Merge pull request 'Avionics test engineering internship content and about the site' (#15) from website-content-updates into main
All checks were successful
Build and Test - Production / test (push) Successful in 4m42s
Build and Test - Production / build_and_push (push) Successful in 5m7s
Build and Test - Production / deploy_production (push) Successful in 2s

Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
2025-12-11 01:15:53 +00:00
14 changed files with 345 additions and 85 deletions

View File

@@ -16,6 +16,7 @@ Dechorionator
ebox ebox
fhhs fhhs
flowbite flowbite
Gitea
HDFS HDFS
headshot headshot
Homelab Homelab

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

View File

@@ -1,10 +1,23 @@
--- ---
import InlineLink from "@components/InlineLink.astro";
const { pathname } = Astro.url;
--- ---
<footer <footer
class="border-t-caperren-green-dark text-caperren-green-dark z-50 flex w-full max-w-full items-center justify-between border-t bg-black px-6 py-2 text-sm" class="border-t-caperren-green-dark text-caperren-green-dark z-50 flex w-full max-w-full items-center justify-between border-t bg-black px-6 py-2 text-sm"
> >
<span>{import.meta.env.PUBLIC_BUILD_ENVIRONMENT || "development"}</span> <span>{import.meta.env.PUBLIC_BUILD_ENVIRONMENT || "development"}</span>
<div>
<InlineLink
class:list={[
"text-caperren-green-dark hover:text-caperren-green",
pathname === "/hobby/this-website"
? "border-caperren-green-dark hover:border-caperren-green border-b-2"
: false,
]}
href="/hobby/this-website">About This Website</InlineLink
>
</div>
<span>{import.meta.env.PUBLIC_REPO_VERSION_HASH || "invalid"}</span> <span>{import.meta.env.PUBLIC_REPO_VERSION_HASH || "invalid"}</span>
</footer> </footer>

View File

@@ -7,16 +7,10 @@ interface Props extends ComponentPropsBase {
} }
const { class: className, href, target } = Astro.props; const { class: className, href, target } = Astro.props;
const { pathname } = Astro.url;
let finalTarget: string | undefined = target; const finalTarget =
target === undefined ? (href.startsWith("/") ? undefined : "_blank") : target;
if (target === undefined) {
if (href.startsWith("/")) {
finalTarget = "";
} else {
finalTarget = "_blank";
}
}
--- ---
<> <>
@@ -24,6 +18,7 @@ if (target === undefined) {
class:list={["text-blue-500", "hover:text-blue-300", className]} class:list={["text-blue-500", "hover:text-blue-300", className]}
href={href} href={href}
target={finalTarget} target={finalTarget}
aria-current={pathname === href ? "page" : undefined}
> >
<slot /> <slot />
</a> </a>

View File

@@ -1,26 +1,18 @@
--- ---
import type { navLink } from "@interfaces/site-layout.ts"; import type { navLink } from "@interfaces/site-layout.ts";
import { getHrefPath, getNavLinkSuffix } from "@data/site-layout.ts";
const items: navLink[] = Astro.props.items; const items: navLink[] = Astro.props.items;
const depth: number = Astro.props.depth ?? 0; const depth: number = Astro.props.depth ?? 0;
const paths: string[] = Astro.props.paths ?? []; const paths: string[] = Astro.props.paths ?? [];
const getNavLinkSuffix = (entry: navLink): string => {
return "-" + [...paths, entry.path].join("-");
};
const getHrefPath = (entry: navLink): string => {
return entry.pubpath
? entry.pubpath
: "/" +
(paths && paths.length ? [...paths, entry.path].join("/") : entry.path);
};
const { pathname } = Astro.url; const { pathname } = Astro.url;
--- ---
<ul <ul
class:list={[ class:list={[
"border-caperren-green flex flex-col space-y-4 space-x-8 bg-black", "border-caperren-green flex flex-col space-y-4 bg-black",
depth depth
? "space-y-4 py-4" ? "space-y-4 py-4"
: "items-start lg:mt-0 lg:flex-row lg:space-y-0 lg:space-x-8", : "items-start lg:mt-0 lg:flex-row lg:space-y-0 lg:space-x-8",
@@ -29,21 +21,22 @@ const { pathname } = Astro.url;
{ {
items.map( items.map(
(entry) => (entry) =>
(entry.enabled ?? true) && ( (entry.enabled ?? true) &&
!(entry.hidden ?? false) && (
<li class=""> <li class="">
{Array.isArray(entry.children) && entry.children.length ? ( {Array.isArray(entry.children) && entry.children.length ? (
<div> <div>
<button <button
id={"dropdownNavbarLink" + getNavLinkSuffix(entry)} id={"dropdownNavbarLink" + getNavLinkSuffix(paths, entry)}
data-dropdown-toggle={ data-dropdown-toggle={
"dropdownNavbar" + getNavLinkSuffix(entry) "dropdownNavbar" + getNavLinkSuffix(paths, entry)
} }
data-dropdown-placement="bottom-start" data-dropdown-placement="bottom-start"
data-dropdown-offset-distance="5" data-dropdown-offset-distance="5"
data-dropdown-offset-skidding="12" data-dropdown-offset-skidding="12"
class:list={[ class:list={[
"hover:text-caperren-green-light lg:hover:text-caperren-green-light flex w-full items-center justify-between lg:p-0 lg:hover:bg-transparent", "hover:text-caperren-green-light lg:hover:text-caperren-green-light flex w-full items-center justify-between lg:p-0 lg:hover:bg-transparent",
pathname.startsWith(getHrefPath(entry)) pathname.startsWith(getHrefPath(paths, entry))
? "border-caperren-green border-b-2" ? "border-caperren-green border-b-2"
: false, : false,
]} ]}
@@ -65,7 +58,7 @@ const { pathname } = Astro.url;
</svg> </svg>
</button> </button>
<div <div
id={"dropdownNavbar" + getNavLinkSuffix(entry)} id={"dropdownNavbar" + getNavLinkSuffix(paths, entry)}
class="border-caperren-green z-10 hidden w-max max-w-screen border bg-black px-6 shadow-sm" class="border-caperren-green z-10 hidden w-max max-w-screen border bg-black px-6 shadow-sm"
> >
<Astro.self <Astro.self
@@ -78,16 +71,18 @@ const { pathname } = Astro.url;
) : ( ) : (
<div> <div>
<a <a
href={getHrefPath(entry)} href={getHrefPath(paths, entry)}
target={getHrefPath(entry).startsWith("/") ? "" : "_blank"} target={
getHrefPath(paths, entry).startsWith("/") ? "" : "_blank"
}
class:list={[ class:list={[
"hover:text-caperren-green-light ring-caperren-green-dark block bg-transparent lg:p-0", "hover:text-caperren-green-light ring-caperren-green-dark block bg-transparent lg:p-0",
pathname === getHrefPath(entry) pathname === getHrefPath(paths, entry)
? "border-caperren-green border-b-2" ? "border-caperren-green border-b-2"
: false, : false,
]} ]}
aria-current={ aria-current={
pathname === getHrefPath(entry) ? "page" : undefined pathname === getHrefPath(paths, entry) ? "page" : undefined
} }
> >
{entry.navText} {entry.navText}

View File

@@ -7,10 +7,14 @@ const keys: { [key: string]: string } = {
ADCP: "Acoustic doppler current profiler", ADCP: "Acoustic doppler current profiler",
COTS: "Consumer off-the-shelf", COTS: "Consumer off-the-shelf",
CTD: "Conductivity, temperature, and depth sensor", CTD: "Conductivity, temperature, and depth sensor",
DUTs: "Devices under test",
GUI: "Graphical user interface", GUI: "Graphical user interface",
NUC: "A small and low-power computer made by Intel", NUC: "A small and low-power computer made by Intel",
PCBs: "Printed circuit boards", PCBs: "Printed circuit boards",
TDD: "Test driven development",
UPS: "Uninterruptible power supply", UPS: "Uninterruptible power supply",
VISA: "Virtual instrument software architecture",
VPS: "Virtual private server",
}; };
const key: string | undefined = Astro.props.key; const key: string | undefined = Astro.props.key;
@@ -21,6 +25,12 @@ if (key && keys.hasOwnProperty(key)) {
word = key; word = key;
definition = keys[key]; definition = keys[key];
} }
if (!word || !definition) {
throw new Error(
`Popover definition is missing! Inputs were\nkey: ${key}\nword: ${word}\ndefinition: ${definition}`,
);
}
--- ---
<> <>

View File

@@ -13,7 +13,11 @@ const { class: className, lineItems, depth = 0 } = Astro.props;
--- ---
<ul <ul
class:list={["list-inside list-disc", className, depth > 0 ? "ps-3" : false]} class:list={[
"list-outside list-disc",
className,
depth > 0 ? "ps-3" : "ms-3",
]}
> >
{ {
lineItems ? ( lineItems ? (

View File

@@ -1,6 +1,7 @@
import type { navLink } from "@interfaces/site-layout.ts"; import type { navLink } from "@interfaces/site-layout.ts";
export const siteLayout: navLink[] = [ export const siteLayout: navLink[] = [
// Standard navbar entries
{ navText: "About", path: "" }, { navText: "About", path: "" },
{ navText: "Education", path: "education" }, { navText: "Education", path: "education" },
{ {
@@ -8,7 +9,6 @@ export const siteLayout: navLink[] = [
path: "experience", path: "experience",
children: [ children: [
{ {
enabled: false,
navText: "SpaceX", navText: "SpaceX",
path: "spacex", path: "spacex",
children: [ children: [
@@ -18,7 +18,6 @@ export const siteLayout: navLink[] = [
path: "hardware-test-engineer-i-ii", path: "hardware-test-engineer-i-ii",
}, },
{ {
enabled: false,
navText: "Avionics Test Engineering Internship", navText: "Avionics Test Engineering Internship",
path: "avionics-test-engineering-internship", path: "avionics-test-engineering-internship",
}, },
@@ -189,6 +188,7 @@ export const siteLayout: navLink[] = [
}, },
{ enabled: false, navText: "NixOS", path: "nixos" }, { enabled: false, navText: "NixOS", path: "nixos" },
{ navText: "Body Mods", path: "body-mods" }, { navText: "Body Mods", path: "body-mods" },
{ navText: "This Website", path: "this-website" },
], ],
}, },
{ {
@@ -270,3 +270,13 @@ export const getPaths = (
} }
return [...new Set(foundPaths)]; return [...new Set(foundPaths)];
}; };
export const getNavLinkSuffix = (paths: string[], entry: navLink): string => {
return "-" + [...paths, entry.path].join("-");
};
export const getHrefPath = (paths: string[], entry: navLink): string => {
return entry.pubpath
? entry.pubpath
: "/" +
(paths && paths.length ? [...paths, entry.path].join("/") : entry.path);
};

View File

@@ -1,5 +1,6 @@
export interface navLink { export interface navLink {
enabled?: boolean; enabled?: boolean;
hidden?: boolean;
navText: string; navText: string;
path?: string; path?: string;
pubpath?: string; pubpath?: string;

View File

@@ -1,16 +1,28 @@
--- ---
import H2 from "@components/H2.astro";
import H3 from "@components/H3.astro";
import Li from "@components/Li.astro";
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro"; import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
import PageGroup from "@components/PageGroup.astro";
import Paragraph from "@components/Paragraph.astro";
import Paragraphs from "@components/Paragraphs.astro";
import PopoverWordDefinition from "@components/PopoverWordDefinition.astro";
import SkillMatrix from "@components/SkillMatrix/SkillMatrix.astro";
import Timeline from "@components/Timeline/Timeline.astro"; import Timeline from "@components/Timeline/Timeline.astro";
import Ul from "@components/Ul.astro";
import ExperienceLayout from "@layouts/ExperienceLayout.astro"; import ExperienceLayout from "@layouts/ExperienceLayout.astro";
import spring_2019_interns from "@assets/experience/spacex/avionics-test-engineering-internship/spring-2019-interns.jpg"; import spring_2019_interns from "@assets/experience/spacex/avionics-test-engineering-internship/spring-2019-interns.jpg";
import swag from "@assets/experience/spacex/avionics-test-engineering-internship/swag.jpg";
import InlineLink from "@components/InlineLink.astro";
import type { carouselGroup } from "@interfaces/image-carousel.ts"; import type { carouselGroup } from "@interfaces/image-carousel.ts";
import type { categorySkills } from "@interfaces/skill-matrix.ts";
import type { timelineEntry } from "@interfaces/timeline.ts"; import type { timelineEntry } from "@interfaces/timeline.ts";
const headerCarouselGroup: carouselGroup = { const headerCarouselGroup: carouselGroup = {
animation: "slide", animation: "slide",
images: [spring_2019_interns], images: [spring_2019_interns, swag],
}; };
const timeline: timelineEntry[] = [ const timeline: timelineEntry[] = [
@@ -23,58 +35,157 @@ const timeline: timelineEntry[] = [
date: "March 2019", date: "March 2019",
}, },
]; ];
const categorizedSkills: categorySkills[] = [
{
category: "Software & Environments",
skills: [
{ item: "Git" },
{
item: "Programming Languages",
subItems: [
{ item: "Python 2/3" },
{ item: "Bash Shell Scripting" },
{ item: "Microsoft SQL" },
],
},
{
item: "Atlassian Suite",
subItems: [
{ item: "Jira" },
{ item: "Bitbucket" },
{ item: "Confluence" },
],
},
{
item: "Automation Interfaces",
subItems: [{ item: "NI MAX/VISA" }, { item: "Jira RESTful APIs" }],
},
{
item: "Operating Systems",
subItems: [
{
item: "Linux",
subItems: [{ item: "Ubuntu" }, { item: "WSL" }],
},
{ item: "Microsoft Windows" },
],
},
],
},
{
category: "Electrical",
skills: [
{
item: "Schematic & PCB Design",
subItems: [{ item: "Altium Designer" }],
},
{
item: "Electrical Diagnostics",
subItems: [{ item: "Multimeters" }],
},
],
},
];
--- ---
<ExperienceLayout title="SpaceX - Avionics Test Engineering Internship"> <ExperienceLayout
title="Avionics Test Engineering Internship"
subTitles={["Space Exploration Technologies Corporation"]}
>
<Carousel carouselGroup={headerCarouselGroup} /> <Carousel carouselGroup={headerCarouselGroup} />
<PageGroup>
<h2 class="my-4 font-bold md:text-2xl">Summary</h2> <Fragment slot="header"><H2>Summary</H2></Fragment>
<h3 class="my-4 font-bold md:text-lg">Timeline</h3> <PageGroup>
<Fragment slot="header"><H3>Timeline</H3></Fragment>
<Timeline timeline={timeline} /> <Timeline timeline={timeline} />
<h3 class="my-4 font-bold md:text-lg">Key Takeaways</h3> </PageGroup>
<ul class="list-inside list-disc"> <PageGroup>
<li></li> <Fragment slot="header"><H3>Key Takeaways</H3></Fragment>
</ul> <Ul>
<h3 class="my-4 font-bold md:text-lg">Skills Used</h3> <Li
<div >Wrote re-usable, unit-tested, and safety-focused test software
class="border-caperren-green relative grid grid-flow-row gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4" components in Python for validating high-pressure transducers</Li
> >
<div> <Li
<div class="text-sm font-extrabold">Software</div> >Assisted in the running of qualification tests against flight
<hr class="text-caperren-green" /> networking hardware</Li
<ul class="list-inside list-disc text-sm"> >
<li>Python</li> <Li
<li>Test Driven Development</li> >Wrote software in Python to automate work-ticket generation in Jira</Li
</ul> >
</div> <Li
<div> >Successfully debugged electrical faults in <PopoverWordDefinition
<div class="text-sm font-extrabold">Electrical</div> key="PCBs"
<hr class="text-caperren-green" /> /> used in Avionics test systems</Li
</div> >
<div> <Li
<div class="text-sm font-extrabold">Other</div> >Directly interfaced with the engineering team for <PopoverWordDefinition
<hr class="text-caperren-green" /> key="COTS"
</div> /> test equipment, driving firmware fixes and providing beta test feedback</Li
</div> >
<h2 class="my-4 font-bold md:text-2xl">Details</h2> </Ul>
</PageGroup>
Though I did get to work on some really fun projects during my internship at <SkillMatrix categorizedSkills={categorizedSkills} />
SpaceX, I unfortunately cant go into much detail due to NDAs and ITAR </PageGroup>
restrictions. What I can say is that I mainly wrote Python for a new avionics <PageGroup>
hardware test system. My experience with writing Python in the numerous other <Fragment slot="header"><H2>Details</H2></Fragment>
projects Ive done really helped me out here, as the framework SpaceX has <Paragraphs>
created was quite complex and would otherwise have been fairly difficult to <Paragraph>
write code for. I also wrote a simple tool for automating the creation of Jira Working at SpaceX was a dream come true, even when it was only for a
work tickets so that the two teams that ended up using it wouldnt have to three month internship. I've always loved space, including the
have their members manually creating dozens of them as work and issues came in technology related to it, as I know many others also do. I grew as a
through a separate system. I was also quite happy in that I got to perform Trekkie, watching endless hours of rubbermaid tubs filled with VHS
some circuit debugging on avionics test system hardware, both for my project recordings of the original series, The Next Generation, and Voyager. As
and for a separate test system. A final experience I had here was getting to someone with a multi-disciplinary background, and with a special focus
work directly with the head engineer from a company that supplied a piece of on robotics and automation, test engineering was a very good fit!
test hardware I was interfacing with. It was quite incredible to see just how </Paragraph>
much weight a SpaceX email address had when trying to solve problems I had <Paragraph>
found with the hardware. Not only were they responsive, but in fact were During this internship, my primary goal was to create re-usable
willing to fast-track firmware updates for us to get things working. Coming components for high pressure test systems. This involved interfacing
from clubs and small labs where a support email might not even get a response with test rack equipment which could apply pressure to <PopoverWordDefinition
for months, it was quite a refreshing experience. key="DUTs"
/>, and would then validate their response and performance to this
stimulus. While I'd written quite a bit of Python prior to this, the
standards for these components were (understandably!) much higher than
I'd encountered previously. This effectively meant that the internship
was a bit of a crash course in <PopoverWordDefinition key="TDD" />, as
being able to verify that the tests would apply the correct stimulus,
and in the correct way, helped reduce the chances of damaging <PopoverWordDefinition
key="DUTs"
/>, or supporting test equipment. As part of this effort, I also made
significant improvements to the driver for the piece of test equipment
which controlled how pressure was applied. While doing so, I encountered
a fair number of discrepancies in the documentation and <PopoverWordDefinition
key="VISA"
/>
commands for this device. I ended up contacting the company and was put in
contact with their engineering department, where I then drove fixes to their
firmware and documentation, ultimately resulting in a fully functional test
setup. Since I ended up <InlineLink
href="/experience/spacex/hardware-test-engineer-i-ii"
>working for the company long-term</InlineLink
>, I know that these components were used, and likely still are, for
many pressure-related validation tests at the company and worked well!
</Paragraph>
<Paragraph>
There were also a couple of side projects I worked on while here. The
first was small Python app which automated the creation of Jira work
tickets for the Lifecycle Engineering Team. Previously, a member of that
team on a rotating schedule would manually create counterpart Jira
tickets associated to a proprietary tracking system created in-house. By
manually querying the SQL database for this proprietary app, and making
it run on a schedule, tickets were made and assigned to the appropriate
teams automatically, improving the response time to these tickets, and
removing the potential for errors during the previously manual
copy/pasting efforts. In my first week, I also debugged a custom circuit
board for a motor controller test system, finding the location of a dead
short, directing a tech to repair the failure, and then verified that
the board functioned correctly afterwards. The final small project I
worked on was manually running the shock tests in the qualification
efforts for a piece of spaceflight networking gear while its owner, a
friend, could not be present.
</Paragraph>
</Paragraphs>
</PageGroup>
</ExperienceLayout> </ExperienceLayout>

View File

@@ -0,0 +1,120 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
import H3 from "@components/H3.astro";
import InlineLink from "@components/InlineLink.astro";
import PageGroup from "@components/PageGroup.astro";
import Paragraph from "@components/Paragraph.astro";
import Paragraphs from "@components/Paragraphs.astro";
import PopoverWordDefinition from "@components/PopoverWordDefinition.astro";
import SkillMatrix from "@components/SkillMatrix/SkillMatrix.astro";
import type { categorySkills } from "@interfaces/skill-matrix.ts";
const categorizedSkills: categorySkills[] = [
{
category: "Software & Environments",
skills: [
{
item: "DevOps",
subItems: [
{ item: "Github Actions" },
{ item: "dev/staging/production environments" },
{ item: "automatic build/test/deploy" },
],
},
{ item: "Git" },
{
item: "Docker",
subItems: [
{ item: "Custom Builds" },
{ item: "Registry & Asset Management" },
],
},
{
item: "Programming Languages",
subItems: [
{ item: "HTML" },
{ item: "CSS" },
{ item: "Typescript" },
{ item: "Bash" },
{ item: "Makefile" },
],
},
{
item: "Web Frameworks",
subItems: [
{ item: "Astro" },
{ item: "tailwindcss" },
{ item: "flowbite" },
],
},
{
item: "Operating Systems",
subItems: [
{
item: "Linux",
subItems: [{ item: "NixOS" }, { item: "Alpine Linux" }],
},
],
},
],
},
];
---
<ExperienceLayout title="This Website" subTitles={["Hobbies"]}>
<PageGroup>
<Fragment slot="header"><H3>Summary</H3></Fragment>
<Paragraphs>
<Paragraph>
While I've traditionally used Wordpress to build my websites in the
past, I finally decided to make a custom one after developing the skills
to do so at <InlineLink
href="/experience/spacex/hardware-test-engineer-i-ii"
>SpaceX</InlineLink
>. I still wouldn't call myself a web developer by trade, but I take
great pride in being able to create things on my own, and have also felt
that Wordpress was overkill for a simple portfolio website. It also gave
me a chance to put my DevOps skills to use at home, which I've been
wanting to do for a while.
</Paragraph>
<Paragraph>
The core framework of my website is <InlineLink
href="https://astro.build/">Astro</InlineLink
>, chosen for its focus on static site generation and de-duplication of
code via re-usable <InlineLink
href="https://docs.astro.build/en/basics/astro-components/"
>components</InlineLink
>. This seemed like the perfect middle-ground of providing enough
structure and quality-of-life features to reduce the overall effort of
building the site, without being overly bloated or opinionated. So far
I've been incredibly happy with this decision, and would recommend it to
others looking to build static websites. To add some helpful styling
utilities, and basic web components, <InlineLink
href="https://tailwindcss.com">tailwindcss</InlineLink
> and <InlineLink href="https://flowbite.com">flowbite</InlineLink> were also
added. Like my decision to use Astro, these both provided good starting points
for creating a website that felt like my own, without struggling for too long
at the start.
</Paragraph>
<Paragraph>
From the DevOps perspective, I created a Makefile within the repo for
local development targets to build, run, and test both in a pure
context, and within a Docker container. After pushing updates on a
branch to my local Gitea instance, and opening a pull request, the
website performs spelling checks, runs unit tests, and integration
tests. Once these pass, the website is built into a Docker container and
uploaded to the registry in my Gitea instance. The image is then
deployed to staging on my <PopoverWordDefinition key="VPS" />, allowing
for manual validation of the changes. In order to merge, the build,
test, and deploy actions must pass, and ideally I should be empirically
validating the staging deployment. After merging and closing the pull
request, another action builds, tests, and deploys the main branch in
the same way as before, but to production, and is what you see here!
</Paragraph>
</Paragraphs>
<SkillMatrix categorizedSkills={categorizedSkills} />
</PageGroup>
</ExperienceLayout>

View File

@@ -1,7 +1,7 @@
--- ---
import ResumeLayout from "@layouts/ResumeLayout.astro"; import ResumeLayout from "@layouts/ResumeLayout.astro";
import resume from "@assets/resume/corwin_perren_2025-10-27-infrastructure_engineer.pdf"; import resume from "@assets/resume/corwin_perren_2025-10-27_infrastructure_engineer.pdf";
--- ---
<ResumeLayout title="2025-10-27 - Infrastructure Engineer" resume={resume} /> <ResumeLayout title="2025-10-27 - Infrastructure Engineer" resume={resume} />