Skip to content

Conversation

@martcpp
Copy link
Member

@martcpp martcpp commented Nov 20, 2025

No description provided.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request implements production-ready CI/CD infrastructure and code quality improvements for the Rust Nigeria website. The changes focus on optimizing Docker builds with cargo-chef, setting up separate deployment workflows for dev and production environments, and applying Rust best practices including removing explicit returns, eliminating unnecessary clones, fixing macro hygiene, and enforcing stricter clippy rules.

Key Changes:

  • Implemented multi-stage Docker build with cargo-chef for optimized dependency caching
  • Added production deployment workflow for Render and separated dev deployment to a dedicated branch
  • Applied idiomatic Rust code improvements (implicit returns, removed unnecessary clones/conversions)
  • Enforced stricter code quality with clippy warnings as errors
  • Temporarily disabled members_jigsaw WebGL feature by commenting out the module

Reviewed Changes

Copilot reviewed 19 out of 22 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Dockerfile Added cargo-chef multi-stage build for faster Docker builds with dependency caching
.github/workflows/prod_deployment.yml New production deployment workflow using Render deploy hooks
.github/workflows/dev_deployment.yml Updated to trigger on dev branch instead of main
cargo_tool.rs Added -D warnings flag to clippy to fail builds on warnings
src/utils/webgl.rs Replaced explicit returns with implicit returns (idiomatic Rust)
src/utils/render_loop.rs Replaced manual Default impl with derive macro
src/utils/load_image.rs Removed explicit return for cleaner code
src/utils/cn.rs Fixed macro hygiene by using $crate:: instead of crate::
src/hooks/use_pagination.rs Removed unnecessary type casts and explicit returns
src/hooks/use_in_view.rs Removed unnecessary clones and dereferences
src/components/nav/mod.rs Added allow attribute for non_snake_case naming and optimized iterator
src/components/hero_section/mod.rs Updated placeholder text with actual content
src/components/events_section/events/event_card.rs Removed unnecessary String::from conversion
src/components/community_section/members_jigsaw/* Commented out entire WebGL module (temporary)
src/components/cards_list/mod.rs Simplified nested if logic and removed unnecessary cast
src/components/articles_section/articles/article_card.rs Added allow unused_variables and removed String conversion
src/components/projects_section/projects/project_card.rs Added allow unused_variables attribute
.gitignore Added *.exe pattern to ignore compiled executables
assets/favicon.ico Added new favicon binary file
cargo_tool.exe Added compiled executable (should not be in version control)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

use leptos::prelude::*;

#[component]
#[allow(unused_variables)]
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The #[allow(unused_variables)] attribute suppresses warnings. Similar to the previous comment, consider using underscore-prefixed names for unused parameters or removing them entirely instead of using a blanket allow attribute.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +223
// #![warn(dead_code)]
// use leptos::{
// html,
// prelude::{Get, NodeRef},
// };

use wasm_bindgen::JsCast;
use web_sys::{
HtmlCanvasElement, HtmlImageElement, WebGlBuffer, WebGlProgram, WebGlRenderingContext as GL,
WebGlTexture, WebGlUniformLocation,
};
// use wasm_bindgen::JsCast;
// use web_sys::{
// HtmlCanvasElement, HtmlImageElement, WebGlBuffer, WebGlProgram, WebGlRenderingContext as GL,
// WebGlTexture, WebGlUniformLocation,
// };

use crate::utils::{
load_image::ImageFuture,
resize_canvas_to_display_size::resize_canvas_to_display_size,
webgl::{
create_program, create_shader, get_canvas_to_clipspace_projection_matrix,
get_texture_to_clipspace_projection_matrix, set_quad,
},
};
// use crate::utils::{
// load_image::ImageFuture,
// resize_canvas_to_display_size::resize_canvas_to_display_size,
// webgl::{
// create_program, create_shader, get_canvas_to_clipspace_projection_matrix,
// get_texture_to_clipspace_projection_matrix, set_quad,
// },
// };

use super::shaders::{FRAGMENT_SHADER, VERTEX_SHADER};
// use super::shaders::{FRAGMENT_SHADER, VERTEX_SHADER};

fn create_texture(gl: &GL, filter: u32) -> WebGlTexture {
let texture = gl.create_texture().unwrap();
gl.bind_texture(GL::TEXTURE_2D, Some(&texture));

gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::CLAMP_TO_EDGE as i32);
gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::CLAMP_TO_EDGE as i32);
gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, filter as i32);
gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, filter as i32);

texture
}

#[derive(Clone)]
pub struct Jigsaw {
gl: GL,
program: WebGlProgram,
vertex_position_attribute_loc: i32,
texture_position_attribute_loc: i32,
vertex_position_buffer: WebGlBuffer,
texture_position_buffer: WebGlBuffer,
canvas_projection_matrix_uniform_loc: WebGlUniformLocation,
canvas: HtmlCanvasElement,
main_image: HtmlImageElement,
duration_uniform_loc: WebGlUniformLocation,
}

impl Jigsaw {
pub async fn new(canvas_ref: NodeRef<html::Canvas>, reveal_duration: f64) -> Self {
let canvas = canvas_ref.get().unwrap();

let gl = canvas
.get_context("webgl")
.unwrap()
.unwrap()
.dyn_into::<GL>()
.unwrap();

let vertex_shader = create_shader(&gl, GL::VERTEX_SHADER, VERTEX_SHADER).unwrap();
let fragment_shader = create_shader(&gl, GL::FRAGMENT_SHADER, FRAGMENT_SHADER).unwrap();

let program = create_program(&gl, &vertex_shader, &fragment_shader).unwrap();

let canvas_projection_matrix_uniform_loc = gl
.get_uniform_location(&program, "u_canvasProjectionMatrix")
.unwrap();

let texture_projection_matrix_uniform_loc = gl
.get_uniform_location(&program, "u_textureProjectionMatrix")
.unwrap();

gl.pixel_storei(GL::UNPACK_FLIP_Y_WEBGL, 1);

let main_image_uniform_loc = gl.get_uniform_location(&program, "u_mainImage").unwrap();

let mask_image_uniform_loc = gl.get_uniform_location(&program, "u_maskImage").unwrap();

let texture_location_uniform_loc = gl
.get_uniform_location(&program, "u_textureResolution")
.unwrap();

let reveal_duration_uniform_loc = gl
.get_uniform_location(&program, "u_revealDuration")
.unwrap();

let duration_uniform_loc = gl.get_uniform_location(&program, "u_duration").unwrap();

let vertex_position_attribute_loc = gl.get_attrib_location(&program, "a_position");

let texture_position_attribute_loc = gl.get_attrib_location(&program, "a_texCoord");

let vertex_position_buffer = gl.create_buffer().unwrap();

let texture_position_buffer = gl.create_buffer().unwrap();

let main_image = ImageFuture::new("/assets/images/puzzle.jpg").await.unwrap();

let mask_image = ImageFuture::new("/assets/images/puzzle-mask.png")
.await
.unwrap();

gl.use_program(Some(&program));

gl.uniform1f(Some(&reveal_duration_uniform_loc), reveal_duration as f32);

gl.uniform2fv_with_f32_array(
Some(&texture_location_uniform_loc),
&[main_image.width() as f32, main_image.height() as f32],
);

gl.uniform_matrix3fv_with_f32_array(
Some(&texture_projection_matrix_uniform_loc),
false,
&get_texture_to_clipspace_projection_matrix(
main_image.width() as f32,
main_image.height() as f32,
),
);

let main_image_texture = create_texture(&gl, GL::LINEAR);
gl.tex_image_2d_with_u32_and_u32_and_image(
GL::TEXTURE_2D,
0,
GL::RGBA as i32,
GL::RGBA,
GL::UNSIGNED_BYTE,
&main_image,
)
.unwrap();
gl.uniform1i(Some(&main_image_uniform_loc), 0);

let mask_image_texture = create_texture(&gl, GL::NEAREST);
gl.tex_image_2d_with_u32_and_u32_and_image(
GL::TEXTURE_2D,
0,
GL::RGBA as i32,
GL::RGBA,
GL::UNSIGNED_BYTE,
&mask_image,
)
.unwrap();
gl.uniform1i(Some(&mask_image_uniform_loc), 1);

gl.active_texture(GL::TEXTURE0);
gl.bind_texture(GL::TEXTURE_2D, Some(&main_image_texture));

gl.active_texture(GL::TEXTURE1);
gl.bind_texture(GL::TEXTURE_2D, Some(&mask_image_texture));

Jigsaw {
gl,
program,
vertex_position_attribute_loc,
texture_position_attribute_loc,
vertex_position_buffer,
texture_position_buffer,
canvas_projection_matrix_uniform_loc,
canvas,
main_image,
duration_uniform_loc,
}
}

pub fn render(&self, duration: f64) {
let Jigsaw {
main_image,
canvas,
gl,
program,
vertex_position_attribute_loc,
vertex_position_buffer,
texture_position_buffer,
texture_position_attribute_loc,
canvas_projection_matrix_uniform_loc,
duration_uniform_loc,
} = self;

gl.use_program(Some(&program));

resize_canvas_to_display_size(&canvas);

let canvas_width = canvas.width() as f32;
let canvas_height = canvas.height() as f32;

gl.uniform_matrix3fv_with_f32_array(
Some(&canvas_projection_matrix_uniform_loc),
false,
&get_canvas_to_clipspace_projection_matrix(canvas_width, canvas_height),
);

gl.uniform1f(Some(&duration_uniform_loc), duration as f32);

gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vertex_position_buffer));
set_quad(&gl, canvas_width, canvas_height);
gl.enable_vertex_attrib_array(vertex_position_attribute_loc.clone() as u32);
gl.vertex_attrib_pointer_with_i32(
vertex_position_attribute_loc.clone() as u32,
2,
GL::FLOAT,
false,
0,
0,
);

gl.bind_buffer(GL::ARRAY_BUFFER, Some(&texture_position_buffer));
set_quad(&gl, main_image.width() as f32, main_image.height() as f32);
gl.enable_vertex_attrib_array(texture_position_attribute_loc.clone() as u32);
gl.vertex_attrib_pointer_with_i32(
texture_position_attribute_loc.clone() as u32,
2,
GL::FLOAT,
false,
0,
0,
);

gl.viewport(0, 0, canvas_width as i32, canvas_height as i32);

gl.draw_arrays(GL::TRIANGLES, 0, 6);
}
}
// fn create_texture(gl: &GL, filter: u32) -> WebGlTexture {
// let texture = gl.create_texture().unwrap();
// gl.bind_texture(GL::TEXTURE_2D, Some(&texture));

// gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::CLAMP_TO_EDGE as i32);
// gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::CLAMP_TO_EDGE as i32);
// gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, filter as i32);
// gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, filter as i32);

// texture
// }

// #[derive(Clone)]
// pub struct Jigsaw {
// gl: GL,
// program: WebGlProgram,
// vertex_position_attribute_loc: i32,
// texture_position_attribute_loc: i32,
// vertex_position_buffer: WebGlBuffer,
// texture_position_buffer: WebGlBuffer,
// canvas_projection_matrix_uniform_loc: WebGlUniformLocation,
// canvas: HtmlCanvasElement,
// main_image: HtmlImageElement,
// duration_uniform_loc: WebGlUniformLocation,
// }

// impl Jigsaw {
// pub async fn new(canvas_ref: NodeRef<html::Canvas>, reveal_duration: f64) -> Self {
// let canvas = canvas_ref.get().unwrap();

// let gl = canvas
// .get_context("webgl")
// .unwrap()
// .unwrap()
// .dyn_into::<GL>()
// .unwrap();

// let vertex_shader = create_shader(&gl, GL::VERTEX_SHADER, VERTEX_SHADER).unwrap();
// let fragment_shader = create_shader(&gl, GL::FRAGMENT_SHADER, FRAGMENT_SHADER).unwrap();

// let program = create_program(&gl, &vertex_shader, &fragment_shader).unwrap();

// let canvas_projection_matrix_uniform_loc = gl
// .get_uniform_location(&program, "u_canvasProjectionMatrix")
// .unwrap();

// let texture_projection_matrix_uniform_loc = gl
// .get_uniform_location(&program, "u_textureProjectionMatrix")
// .unwrap();

// gl.pixel_storei(GL::UNPACK_FLIP_Y_WEBGL, 1);

// let main_image_uniform_loc = gl.get_uniform_location(&program, "u_mainImage").unwrap();

// let mask_image_uniform_loc = gl.get_uniform_location(&program, "u_maskImage").unwrap();

// let texture_location_uniform_loc = gl
// .get_uniform_location(&program, "u_textureResolution")
// .unwrap();

// let reveal_duration_uniform_loc = gl
// .get_uniform_location(&program, "u_revealDuration")
// .unwrap();

// let duration_uniform_loc = gl.get_uniform_location(&program, "u_duration").unwrap();

// let vertex_position_attribute_loc = gl.get_attrib_location(&program, "a_position");

// let texture_position_attribute_loc = gl.get_attrib_location(&program, "a_texCoord");

// let vertex_position_buffer = gl.create_buffer().unwrap();

// let texture_position_buffer = gl.create_buffer().unwrap();

// let main_image = ImageFuture::new("/assets/images/puzzle.jpg").await.unwrap();

// let mask_image = ImageFuture::new("/assets/images/puzzle-mask.png")
// .await
// .unwrap();

// gl.use_program(Some(&program));

// gl.uniform1f(Some(&reveal_duration_uniform_loc), reveal_duration as f32);

// gl.uniform2fv_with_f32_array(
// Some(&texture_location_uniform_loc),
// &[main_image.width() as f32, main_image.height() as f32],
// );

// gl.uniform_matrix3fv_with_f32_array(
// Some(&texture_projection_matrix_uniform_loc),
// false,
// &get_texture_to_clipspace_projection_matrix(
// main_image.width() as f32,
// main_image.height() as f32,
// ),
// );

// let main_image_texture = create_texture(&gl, GL::LINEAR);
// gl.tex_image_2d_with_u32_and_u32_and_image(
// GL::TEXTURE_2D,
// 0,
// GL::RGBA as i32,
// GL::RGBA,
// GL::UNSIGNED_BYTE,
// &main_image,
// )
// .unwrap();
// gl.uniform1i(Some(&main_image_uniform_loc), 0);

// let mask_image_texture = create_texture(&gl, GL::NEAREST);
// gl.tex_image_2d_with_u32_and_u32_and_image(
// GL::TEXTURE_2D,
// 0,
// GL::RGBA as i32,
// GL::RGBA,
// GL::UNSIGNED_BYTE,
// &mask_image,
// )
// .unwrap();
// gl.uniform1i(Some(&mask_image_uniform_loc), 1);

// gl.active_texture(GL::TEXTURE0);
// gl.bind_texture(GL::TEXTURE_2D, Some(&main_image_texture));

// gl.active_texture(GL::TEXTURE1);
// gl.bind_texture(GL::TEXTURE_2D, Some(&mask_image_texture));

// Jigsaw {
// gl,
// program,
// vertex_position_attribute_loc,
// texture_position_attribute_loc,
// vertex_position_buffer,
// texture_position_buffer,
// canvas_projection_matrix_uniform_loc,
// canvas,
// main_image,
// duration_uniform_loc,
// }
// }

// pub fn render(&self, duration: f64) {
// let Jigsaw {
// main_image,
// canvas,
// gl,
// program,
// vertex_position_attribute_loc,
// vertex_position_buffer,
// texture_position_buffer,
// texture_position_attribute_loc,
// canvas_projection_matrix_uniform_loc,
// duration_uniform_loc,
// } = self;

// gl.use_program(Some(program));

// resize_canvas_to_display_size(canvas);

// let canvas_width = canvas.width() as f32;
// let canvas_height = canvas.height() as f32;

// gl.uniform_matrix3fv_with_f32_array(
// Some(canvas_projection_matrix_uniform_loc),
// false,
// &get_canvas_to_clipspace_projection_matrix(canvas_width, canvas_height),
// );

// gl.uniform1f(Some(duration_uniform_loc), duration as f32);

// gl.bind_buffer(GL::ARRAY_BUFFER, Some(vertex_position_buffer));
// set_quad(gl, canvas_width, canvas_height);
// gl.enable_vertex_attrib_array(*vertex_position_attribute_loc as u32);
// gl.vertex_attrib_pointer_with_i32(
// *vertex_position_attribute_loc as u32,
// 2,
// GL::FLOAT,
// false,
// 0,
// 0,
// );

// gl.bind_buffer(GL::ARRAY_BUFFER, Some(texture_position_buffer));
// set_quad(gl, main_image.width() as f32, main_image.height() as f32);
// gl.enable_vertex_attrib_array(*texture_position_attribute_loc as u32);
// gl.vertex_attrib_pointer_with_i32(
// *texture_position_attribute_loc as u32,
// 2,
// GL::FLOAT,
// false,
// 0,
// 0,
// );

// gl.viewport(0, 0, canvas_width as i32, canvas_height as i32);

// gl.draw_arrays(GL::TRIANGLES, 0, 6);
// }
// }
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The entire contents of jigsaw.rs, shaders.rs, and mod.rs in the members_jigsaw module have been commented out rather than removed. If this code is no longer needed, it should be deleted entirely. If it's temporarily disabled for production but may be needed later, consider using feature flags instead of commenting out the entire module, or document why it's commented out with a clear TODO or FIXME comment explaining when it will be re-enabled.

Copilot uses AI. Check for mistakes.
use leptos::prelude::*;

#[component]
#[allow(unused_variables)]
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The #[allow(unused_variables)] attribute suppresses warnings about unused variables in this component. Instead of suppressing the warning, consider using an underscore prefix for genuinely unused parameters (e.g., _class) or removing them if they're not needed. This makes the intent clearer and doesn't hide potential issues.

Copilot uses AI. Check for mistakes.
gl_FragColor = vec4(color, opacity);
}
";
// #![warn(dead_code)]
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The commented-out #![warn(dead_code)] at the top of these files suggests there might be dead code warnings. Since the entire module is commented out, consider either removing these files entirely or using a feature flag to conditionally compile this code.

Copilot uses AI. Check for mistakes.
COPY --from=builder /work/Cargo.toml /app/

ENV RUST_LOG="info"
ENV RUST_LOG="debug"
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

Changing the log level from "info" to "debug" in production may have performance implications and could expose sensitive information in logs. Consider using "info" for production and only use "debug" for development/staging environments.

Suggested change
ENV RUST_LOG="debug"
ENV RUST_LOG="info"

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@martcpp martcpp merged commit 02b1e6d into main Nov 20, 2025
2 checks passed
martcpp added a commit that referenced this pull request Nov 20, 2025
Merge pull request #37 from Rust-Nigeria/dev
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants